|
|
@@ -0,0 +1,592 @@
|
|
|
+package shop.alien.store.service.impl;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+import shop.alien.entity.store.UserReservation;
|
|
|
+import shop.alien.entity.store.UserReservationOrder;
|
|
|
+import shop.alien.entity.store.vo.StoreReservationListVo;
|
|
|
+import shop.alien.mapper.StoreReservationMapper;
|
|
|
+import shop.alien.store.config.BaseRedisService;
|
|
|
+import shop.alien.store.listener.RedisKeyExpirationHandler;
|
|
|
+import shop.alien.store.service.StoreReservationService;
|
|
|
+import shop.alien.store.service.UserReservationOrderService;
|
|
|
+import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
+import javax.annotation.PostConstruct;
|
|
|
+
|
|
|
+import java.text.ParseException;
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
+import java.util.Calendar;
|
|
|
+import java.util.Date;
|
|
|
+import java.util.List;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 商家端预约管理 服务实现类
|
|
|
+ *
|
|
|
+ * @author system
|
|
|
+ * @since 2025-01-XX
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+@Transactional(rollbackFor = Exception.class)
|
|
|
+@RequiredArgsConstructor
|
|
|
+public class StoreReservationServiceImpl extends ServiceImpl<StoreReservationMapper, UserReservation> implements StoreReservationService {
|
|
|
+
|
|
|
+ private final UserReservationOrderService userReservationOrderService;
|
|
|
+ private final BaseRedisService baseRedisService;
|
|
|
+ private final RedisKeyExpirationHandler expirationHandler;
|
|
|
+ private final ObjectMapper objectMapper = new ObjectMapper();
|
|
|
+
|
|
|
+ /** 预约状态:已取消 */
|
|
|
+ private static final int STATUS_CANCELLED = 3;
|
|
|
+ /** Redis key前缀:预订核销 */
|
|
|
+ private static final String RESERVATION_VERIFY_PREFIX = "reservation:verify:";
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 初始化时注册预订核销过期处理器
|
|
|
+ */
|
|
|
+ @PostConstruct
|
|
|
+ public void initReservationExpirationHandler() {
|
|
|
+ // 注册预订核销过期处理器
|
|
|
+ expirationHandler.registerHandler(RESERVATION_VERIFY_PREFIX, this::handleExpiredReservationKey);
|
|
|
+ log.info("预订核销过期处理器注册完成,前缀: {}", RESERVATION_VERIFY_PREFIX);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<StoreReservationListVo> getStoreReservationList(Integer storeId, Integer status, Date dateFrom, Date dateTo, Integer orderStatus) {
|
|
|
+ log.info("StoreReservationServiceImpl.getStoreReservationList?storeId={}, status={}, dateFrom={}, dateTo={}, orderStatus={}",
|
|
|
+ storeId, status, dateFrom, dateTo, orderStatus);
|
|
|
+
|
|
|
+ if (storeId == null) {
|
|
|
+ throw new RuntimeException("门店ID不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ return baseMapper.getStoreReservationList(storeId, status, dateFrom, dateTo, orderStatus);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean cancelReservationByStore(Integer reservationId) {
|
|
|
+ log.info("StoreReservationServiceImpl.cancelReservationByStore?reservationId={}", reservationId);
|
|
|
+
|
|
|
+ if (reservationId == null) {
|
|
|
+ throw new RuntimeException("预约ID不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询预约信息
|
|
|
+ UserReservation reservation = this.getById(reservationId);
|
|
|
+ if (reservation == null) {
|
|
|
+ throw new RuntimeException("预约不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查预约状态,已取消的不能再次取消
|
|
|
+ if (reservation.getStatus() != null && reservation.getStatus() == STATUS_CANCELLED) {
|
|
|
+ throw new RuntimeException("预约已取消,不能重复取消");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询关联的订单信息
|
|
|
+ LambdaQueryWrapper<UserReservationOrder> orderWrapper = new LambdaQueryWrapper<>();
|
|
|
+ orderWrapper.eq(UserReservationOrder::getReservationId, reservationId);
|
|
|
+ UserReservationOrder order = userReservationOrderService.getOne(orderWrapper);
|
|
|
+
|
|
|
+ if (order == null) {
|
|
|
+ // 如果没有订单,直接更新预约状态为3(已取消)
|
|
|
+ reservation.setStatus(STATUS_CANCELLED);
|
|
|
+ boolean updateResult = this.updateById(reservation);
|
|
|
+ if (!updateResult) {
|
|
|
+ throw new RuntimeException("更新预约状态失败");
|
|
|
+ }
|
|
|
+ log.info("商家端取消预约成功(无订单),reservationId={}", reservationId);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 判断订单费用类型:0-免费, 1-收费
|
|
|
+ Integer orderCostType = order.getOrderCostType();
|
|
|
+ if (orderCostType == null) {
|
|
|
+ orderCostType = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (orderCostType == 0) {
|
|
|
+ // 免费订单:更新订单状态为4(已取消),更新预约状态为3(已取消)
|
|
|
+ order.setOrderStatus(4); // 4:已取消
|
|
|
+ boolean orderUpdateResult = userReservationOrderService.updateById(order);
|
|
|
+ if (!orderUpdateResult) {
|
|
|
+ throw new RuntimeException("更新订单状态失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ reservation.setStatus(STATUS_CANCELLED);
|
|
|
+ boolean reservationUpdateResult = this.updateById(reservation);
|
|
|
+ if (!reservationUpdateResult) {
|
|
|
+ throw new RuntimeException("更新预约状态失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ log.info("商家端取消预约成功(免费订单),reservationId={}, orderId={}", reservationId, order.getId());
|
|
|
+ return true;
|
|
|
+ } else if (orderCostType == 1) {
|
|
|
+ // 付费订单:功能预留(暂不更新状态,等待后续实现退款逻辑)
|
|
|
+ throw new RuntimeException("付费订单取消功能暂未实现,请稍后再试");
|
|
|
+ } else {
|
|
|
+ throw new RuntimeException("订单费用类型异常,orderCostType=" + orderCostType);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean deleteReservationByStore(Integer reservationId) {
|
|
|
+ log.info("StoreReservationServiceImpl.deleteReservationByStore?reservationId={}", reservationId);
|
|
|
+
|
|
|
+ if (reservationId == null) {
|
|
|
+ throw new RuntimeException("预约ID不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询预约信息
|
|
|
+ UserReservation reservation = this.getById(reservationId);
|
|
|
+ if (reservation == null) {
|
|
|
+ throw new RuntimeException("预约不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询关联的订单信息
|
|
|
+ LambdaQueryWrapper<UserReservationOrder> orderWrapper = new LambdaQueryWrapper<>();
|
|
|
+ orderWrapper.eq(UserReservationOrder::getReservationId, reservationId);
|
|
|
+ UserReservationOrder order = userReservationOrderService.getOne(orderWrapper);
|
|
|
+
|
|
|
+ if (order == null) {
|
|
|
+ // 如果没有订单,直接删除预约记录
|
|
|
+ boolean deleteResult = this.removeById(reservationId);
|
|
|
+ if (!deleteResult) {
|
|
|
+ throw new RuntimeException("删除预约记录失败");
|
|
|
+ }
|
|
|
+ log.info("商家端删除预订信息成功(无订单),reservationId={}", reservationId);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 判断订单状态:只有已取消(4)、已退款(7)、已完成(2)状态才能删除
|
|
|
+ Integer orderStatus = order.getOrderStatus();
|
|
|
+ if (orderStatus == null) {
|
|
|
+ throw new RuntimeException("订单状态异常,无法删除");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 定义可删除的订单状态:2:已完成, 4:已取消, 7:已退款
|
|
|
+ boolean canDelete = orderStatus == 2 || orderStatus == 4 || orderStatus == 7;
|
|
|
+
|
|
|
+ if (!canDelete) {
|
|
|
+ String statusText = getOrderStatusText(orderStatus);
|
|
|
+ throw new RuntimeException("订单状态为" + statusText + ",不允许删除。只有已取消、已退款、已完成状态的订单可以删除");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 删除订单记录(逻辑删除)
|
|
|
+ boolean orderDeleteResult = userReservationOrderService.removeById(order.getId());
|
|
|
+ if (!orderDeleteResult) {
|
|
|
+ throw new RuntimeException("删除订单记录失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 删除预约记录(逻辑删除)
|
|
|
+ boolean reservationDeleteResult = this.removeById(reservationId);
|
|
|
+ if (!reservationDeleteResult) {
|
|
|
+ throw new RuntimeException("删除预约记录失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ log.info("商家端删除预订信息成功,reservationId={}, orderId={}, orderStatus={}",
|
|
|
+ reservationId, order.getId(), orderStatus);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean addTimeByStore(Integer reservationId, String addTimeStart, Integer addTimeMinutes) {
|
|
|
+ log.info("StoreReservationServiceImpl.addTimeByStore?reservationId={}, addTimeStart={}, addTimeMinutes={}",
|
|
|
+ reservationId, addTimeStart, addTimeMinutes);
|
|
|
+
|
|
|
+ if (reservationId == null) {
|
|
|
+ throw new RuntimeException("预约ID不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (addTimeStart == null || addTimeStart.trim().isEmpty()) {
|
|
|
+ throw new RuntimeException("加时开始时间不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (addTimeMinutes == null || addTimeMinutes <= 0) {
|
|
|
+ throw new RuntimeException("加时分钟数必须大于0");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证时间格式 HH:mm
|
|
|
+ if (!addTimeStart.matches("^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$")) {
|
|
|
+ throw new RuntimeException("加时开始时间格式错误,应为HH:mm格式");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询预约信息
|
|
|
+ UserReservation reservation = this.getById(reservationId);
|
|
|
+ if (reservation == null) {
|
|
|
+ throw new RuntimeException("预约不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存原结束时间用于日志
|
|
|
+ String oldEndTime = reservation.getEndTime();
|
|
|
+
|
|
|
+ // 计算新的结束时间
|
|
|
+ // 如果加时开始时间在当前预订结束时间之内,新的结束时间 = 当前结束时间 + 加时分钟数
|
|
|
+ // 如果加时开始时间超过了当前预订结束时间,新的结束时间 = 加时开始时间 + 加时分钟数
|
|
|
+ String newEndTime;
|
|
|
+ if (compareTime(addTimeStart, reservation.getEndTime()) <= 0) {
|
|
|
+ // 加时开始时间在预订结束时间之内,从当前结束时间开始加时
|
|
|
+ newEndTime = calculateNewEndTime(reservation.getEndTime(), addTimeMinutes);
|
|
|
+ } else {
|
|
|
+ // 加时开始时间超过了预订结束时间,从加时开始时间开始加时
|
|
|
+ newEndTime = calculateNewEndTime(addTimeStart, addTimeMinutes);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 校验:不能超过下一个已确认预约的开始时间
|
|
|
+ validateAddTimeNotExceedNextReservation(reservation, addTimeStart, newEndTime);
|
|
|
+
|
|
|
+ // 更新预约结束时间
|
|
|
+ reservation.setEndTime(newEndTime);
|
|
|
+ boolean updateResult = this.updateById(reservation);
|
|
|
+ if (!updateResult) {
|
|
|
+ throw new RuntimeException("更新预约结束时间失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ log.info("商家端加时成功,reservationId={}, 原结束时间={}, 加时开始时间={}, 加时分钟数={}, 新结束时间={}",
|
|
|
+ reservationId, oldEndTime, addTimeStart, addTimeMinutes, newEndTime);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public boolean verifyReservationByNo(String reservationNo) {
|
|
|
+ log.info("StoreReservationServiceImpl.verifyReservationByNo?reservationNo={}", reservationNo);
|
|
|
+
|
|
|
+ if (reservationNo == null || reservationNo.trim().isEmpty()) {
|
|
|
+ throw new RuntimeException("预约号不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 根据预约号查询预约信息
|
|
|
+ LambdaQueryWrapper<UserReservation> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(UserReservation::getReservationNo, reservationNo)
|
|
|
+ .eq(UserReservation::getDeleteFlag, 0)
|
|
|
+ .last("LIMIT 1");
|
|
|
+ UserReservation reservation = this.getOne(wrapper);
|
|
|
+
|
|
|
+ if (reservation == null) {
|
|
|
+ throw new RuntimeException("预约不存在或已被删除");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 校验预约状态(必须是已确认状态才能核销)
|
|
|
+ if (reservation.getStatus() == null || reservation.getStatus() != 1) {
|
|
|
+ throw new RuntimeException("预约状态不正确,只有已确认的预约才能核销");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 校验是否过期:当前时间是否超过预约结束时间
|
|
|
+ if (isReservationExpired(reservation)) {
|
|
|
+ throw new RuntimeException("预约已过期,无法核销");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询关联的订单
|
|
|
+ LambdaQueryWrapper<UserReservationOrder> orderWrapper = new LambdaQueryWrapper<>();
|
|
|
+ orderWrapper.eq(UserReservationOrder::getReservationId, reservation.getId())
|
|
|
+ .eq(UserReservationOrder::getDeleteFlag, 0)
|
|
|
+ .last("LIMIT 1");
|
|
|
+ UserReservationOrder order = userReservationOrderService.getOne(orderWrapper);
|
|
|
+
|
|
|
+ if (order == null) {
|
|
|
+ throw new RuntimeException("未找到关联的订单信息");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新预约状态为已到店(status = 2)
|
|
|
+ reservation.setStatus(2); // 已到店
|
|
|
+ reservation.setActualArrivalTime(new Date());
|
|
|
+ boolean updateReservation = this.updateById(reservation);
|
|
|
+ if (!updateReservation) {
|
|
|
+ throw new RuntimeException("更新预约状态失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新订单状态为已完成(order_status = 2)
|
|
|
+ order.setOrderStatus(2); // 已完成
|
|
|
+ boolean updateOrder = userReservationOrderService.updateById(order);
|
|
|
+ if (!updateOrder) {
|
|
|
+ throw new RuntimeException("更新订单状态失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算Redis过期时间(预订结束时间 + 3小时)
|
|
|
+ long expireSeconds = calculateExpireSecondsWithBuffer(reservation);
|
|
|
+
|
|
|
+ // 将预订信息存储到Redis
|
|
|
+ try {
|
|
|
+ String redisKey = RESERVATION_VERIFY_PREFIX + reservationNo;
|
|
|
+ String reservationJson = objectMapper.writeValueAsString(reservation);
|
|
|
+ baseRedisService.setString(redisKey, reservationJson, expireSeconds);
|
|
|
+ log.info("预订信息已存储到Redis,key={}, expireSeconds={}(预订结束时间+3小时)", redisKey, expireSeconds);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("存储预订信息到Redis失败", e);
|
|
|
+ // Redis存储失败不影响核销流程,只记录日志
|
|
|
+ }
|
|
|
+
|
|
|
+ log.info("核销预约订单成功,reservationNo={}, reservationId={}, orderId={}",
|
|
|
+ reservationNo, reservation.getId(), order.getId());
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public boolean refundReservation(Integer reservationId) {
|
|
|
+ log.info("StoreReservationServiceImpl.refundReservation?reservationId={}", reservationId);
|
|
|
+
|
|
|
+ if (reservationId == null) {
|
|
|
+ throw new RuntimeException("预约ID不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询预约信息
|
|
|
+ UserReservation reservation = this.getById(reservationId);
|
|
|
+ if (reservation == null) {
|
|
|
+ throw new RuntimeException("预约不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询关联的订单
|
|
|
+ LambdaQueryWrapper<UserReservationOrder> orderWrapper = new LambdaQueryWrapper<>();
|
|
|
+ orderWrapper.eq(UserReservationOrder::getReservationId, reservationId)
|
|
|
+ .eq(UserReservationOrder::getDeleteFlag, 0)
|
|
|
+ .last("LIMIT 1");
|
|
|
+ UserReservationOrder order = userReservationOrderService.getOne(orderWrapper);
|
|
|
+
|
|
|
+ if (order == null) {
|
|
|
+ throw new RuntimeException("未找到关联的订单信息");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查订单状态,只有已完成的订单才能退款
|
|
|
+ if (order.getOrderStatus() == null || order.getOrderStatus() != 2) {
|
|
|
+ log.warn("订单状态不正确,无法退款,订单ID: {}, 订单状态: {}", order.getId(), order.getOrderStatus());
|
|
|
+ throw new RuntimeException("订单状态不正确,只有已完成的订单才能退款");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查订单费用类型,只有付费订单才需要退款
|
|
|
+ if (order.getOrderCostType() == null || order.getOrderCostType() == 0) {
|
|
|
+ log.info("免费订单无需退款,订单ID: {}, 预约ID: {}", order.getId(), reservationId);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ // TODO: 调用支付退款接口
|
|
|
+ // 1. 调用第三方支付退款接口(微信支付、支付宝等)
|
|
|
+ // 2. 根据退款结果更新订单状态
|
|
|
+
|
|
|
+ // 更新订单状态为已退款(order_status = 7)
|
|
|
+ order.setOrderStatus(7); // 已退款
|
|
|
+ order.setRefundTime(new Date());
|
|
|
+ boolean updateOrder = userReservationOrderService.updateById(order);
|
|
|
+ if (!updateOrder) {
|
|
|
+ throw new RuntimeException("更新订单状态失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新预约状态为已取消(status = 3)
|
|
|
+ reservation.setStatus(3); // 已取消
|
|
|
+ boolean updateReservation = this.updateById(reservation);
|
|
|
+ if (!updateReservation) {
|
|
|
+ throw new RuntimeException("更新预约状态失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ log.info("预订退款处理成功,预约ID: {}, 订单ID: {}", reservationId, order.getId());
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 校验加时不能超过下一个已确认预约的开始时间
|
|
|
+ */
|
|
|
+ private void validateAddTimeNotExceedNextReservation(UserReservation reservation, String addTimeStart, String newEndTime) {
|
|
|
+ // 确定查询基准时间:取当前结束时间和加时开始时间的较大值
|
|
|
+ String queryBaseTime = reservation.getEndTime();
|
|
|
+ if (compareTime(addTimeStart, reservation.getEndTime()) > 0) {
|
|
|
+ queryBaseTime = addTimeStart;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询同一门店、同一日期、状态为已确认(status=1)的预约
|
|
|
+ LambdaQueryWrapper<UserReservation> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(UserReservation::getStoreId, reservation.getStoreId())
|
|
|
+ .apply("DATE(user_reservation.reservation_date) = DATE({0})", reservation.getReservationDate())
|
|
|
+ .eq(UserReservation::getStatus, 1) // 已确认
|
|
|
+ .ge(UserReservation::getStartTime, queryBaseTime)
|
|
|
+ .ne(UserReservation::getId, reservation.getId())
|
|
|
+ .eq(UserReservation::getDeleteFlag, 0)
|
|
|
+ .orderByAsc(UserReservation::getStartTime)
|
|
|
+ .last("LIMIT 1");
|
|
|
+
|
|
|
+ UserReservation nextReservation = this.getOne(wrapper);
|
|
|
+
|
|
|
+ if (nextReservation != null) {
|
|
|
+ if (compareTime(newEndTime, nextReservation.getStartTime()) > 0) {
|
|
|
+ throw new RuntimeException(
|
|
|
+ String.format("新的结束时间 %s 超过了下一个已确认预约的开始时间 %s(预约号:%s)",
|
|
|
+ newEndTime, nextReservation.getStartTime(), nextReservation.getReservationNo()));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 比较两个时间字符串(HH:mm格式)
|
|
|
+ */
|
|
|
+ private int compareTime(String time1, String time2) {
|
|
|
+ try {
|
|
|
+ SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");
|
|
|
+ Date date1 = sdf.parse(time1);
|
|
|
+ Date date2 = sdf.parse(time2);
|
|
|
+ return date1.compareTo(date2);
|
|
|
+ } catch (ParseException e) {
|
|
|
+ log.error("比较时间失败,time1={}, time2={}", time1, time2, e);
|
|
|
+ throw new RuntimeException("时间比较失败:" + e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算新的结束时间:加时开始时间 + 加时分钟数
|
|
|
+ */
|
|
|
+ private String calculateNewEndTime(String addTimeStart, Integer addTimeMinutes) {
|
|
|
+ try {
|
|
|
+ SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");
|
|
|
+ Date startDate = sdf.parse(addTimeStart);
|
|
|
+
|
|
|
+ Calendar calendar = Calendar.getInstance();
|
|
|
+ calendar.setTime(startDate);
|
|
|
+ calendar.add(Calendar.MINUTE, addTimeMinutes);
|
|
|
+
|
|
|
+ return sdf.format(calendar.getTime());
|
|
|
+ } catch (ParseException e) {
|
|
|
+ log.error("计算新的结束时间失败,addTimeStart={}, addTimeMinutes={}", addTimeStart, addTimeMinutes, e);
|
|
|
+ throw new RuntimeException("时间计算失败:" + e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 校验预约是否过期
|
|
|
+ */
|
|
|
+ private boolean isReservationExpired(UserReservation reservation) {
|
|
|
+ try {
|
|
|
+ Date now = new Date();
|
|
|
+ String endTime = reservation.getEndTime();
|
|
|
+
|
|
|
+ if (endTime == null || endTime.trim().isEmpty()) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ SimpleDateFormat[] dateTimeFormats = {
|
|
|
+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"),
|
|
|
+ new SimpleDateFormat("yyyy-MM-dd HH:mm"),
|
|
|
+ new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"),
|
|
|
+ new SimpleDateFormat("yyyy/MM/dd HH:mm")
|
|
|
+ };
|
|
|
+
|
|
|
+ Date endDateTime = null;
|
|
|
+ for (SimpleDateFormat format : dateTimeFormats) {
|
|
|
+ try {
|
|
|
+ endDateTime = format.parse(endTime);
|
|
|
+ break;
|
|
|
+ } catch (ParseException e) {
|
|
|
+ // 继续尝试下一个格式
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (endDateTime == null) {
|
|
|
+ log.error("无法解析结束时间格式,endTime={}", endTime);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ return now.after(endDateTime);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("校验预约是否过期失败", e);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算Redis过期时间(秒):从当前时间到预订结束时间 + 3小时的秒数
|
|
|
+ */
|
|
|
+ private long calculateExpireSecondsWithBuffer(UserReservation reservation) {
|
|
|
+ try {
|
|
|
+ Date now = new Date();
|
|
|
+ String endTime = reservation.getEndTime();
|
|
|
+
|
|
|
+ if (endTime == null || endTime.trim().isEmpty()) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ SimpleDateFormat[] dateTimeFormats = {
|
|
|
+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"),
|
|
|
+ new SimpleDateFormat("yyyy-MM-dd HH:mm"),
|
|
|
+ new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"),
|
|
|
+ new SimpleDateFormat("yyyy/MM/dd HH:mm")
|
|
|
+ };
|
|
|
+
|
|
|
+ Date endDateTime = null;
|
|
|
+ for (SimpleDateFormat format : dateTimeFormats) {
|
|
|
+ try {
|
|
|
+ endDateTime = format.parse(endTime);
|
|
|
+ break;
|
|
|
+ } catch (ParseException e) {
|
|
|
+ // 继续尝试下一个格式
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (endDateTime == null) {
|
|
|
+ log.error("无法解析结束时间格式,endTime={}", endTime);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 在结束时间基础上加3小时
|
|
|
+ Calendar calendar = Calendar.getInstance();
|
|
|
+ calendar.setTime(endDateTime);
|
|
|
+ calendar.add(Calendar.HOUR_OF_DAY, 3);
|
|
|
+ Date expireDateTime = calendar.getTime();
|
|
|
+
|
|
|
+ long diff = expireDateTime.getTime() - now.getTime();
|
|
|
+ return diff > 0 ? diff / 1000 : 0;
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("计算Redis过期时间失败", e);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理过期的预订核销key
|
|
|
+ */
|
|
|
+ private void handleExpiredReservationKey(String expiredKey) {
|
|
|
+ try {
|
|
|
+ String reservationNo = expiredKey.replace(RESERVATION_VERIFY_PREFIX, "");
|
|
|
+
|
|
|
+ log.info("检测到预订核销key过期,预约号: {}, key: {}", reservationNo, expiredKey);
|
|
|
+
|
|
|
+ LambdaQueryWrapper<UserReservation> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(UserReservation::getReservationNo, reservationNo)
|
|
|
+ .eq(UserReservation::getDeleteFlag, 0)
|
|
|
+ .last("LIMIT 1");
|
|
|
+ UserReservation reservation = this.getOne(wrapper);
|
|
|
+
|
|
|
+ if (reservation == null) {
|
|
|
+ log.warn("预订核销key过期,但未找到对应的预约信息,预约号: {}", reservationNo);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ log.info("开始处理预订退款,预约号: {}, 预约ID: {}", reservationNo, reservation.getId());
|
|
|
+ refundReservation(reservation.getId());
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("处理过期预订核销key失败,key: {}", expiredKey, e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取订单状态文本
|
|
|
+ */
|
|
|
+ private String getOrderStatusText(Integer orderStatus) {
|
|
|
+ if (orderStatus == null) {
|
|
|
+ return "未知";
|
|
|
+ }
|
|
|
+ switch (orderStatus) {
|
|
|
+ case 0: return "待支付";
|
|
|
+ case 1: return "待使用";
|
|
|
+ case 2: return "已完成";
|
|
|
+ case 3: return "已过期";
|
|
|
+ case 4: return "已取消";
|
|
|
+ case 5: return "已关闭";
|
|
|
+ case 6: return "退款中";
|
|
|
+ case 7: return "已退款";
|
|
|
+ case 8: return "商家预订";
|
|
|
+ default: return "未知";
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|