|
|
@@ -71,6 +71,7 @@ public class UserReservationServiceImpl extends ServiceImpl<UserReservationMappe
|
|
|
private final BaseRedisService baseRedisService;
|
|
|
private final AliSms aliSms;
|
|
|
private final ReservationNoticeAsyncService reservationNoticeAsyncService;
|
|
|
+ private final ArrivalReminderNoticeService arrivalReminderNoticeService;
|
|
|
|
|
|
private ReservationOrderPageService reservationOrderPageService;
|
|
|
|
|
|
@@ -95,10 +96,11 @@ public class UserReservationServiceImpl extends ServiceImpl<UserReservationMappe
|
|
|
private static final int MAX_DAYS_TO_CHECK = 366;
|
|
|
/** 全天预订时使用的结束分钟数(24*60,即到次日0点) */
|
|
|
private static final int MINUTES_DAY_END = 24 * 60;
|
|
|
- /** 到店提醒 Redis key 前缀,用于防重复发送 */
|
|
|
- private static final String REDIS_KEY_ARRIVAL_REMINDER_PREFIX = "reservation:arrival:reminder:order:";
|
|
|
- /** 到店提醒已发送标记的 Redis 过期时间(25 分钟) */
|
|
|
- private static final long ARRIVAL_REMINDER_SENT_TTL_SECONDS = 30 * 60;
|
|
|
+ /** 到店提醒:短信防重(保持与原 key 一致,避免已标记订单重复发短信) */
|
|
|
+ private static final String REDIS_KEY_ARRIVAL_SMS_PREFIX = "reservation:arrival:reminder:order:";
|
|
|
+ /** 到店提醒:站内通知防重(与短信独立,短信失败可多次重试) */
|
|
|
+ private static final String REDIS_KEY_ARRIVAL_NOTICE_PREFIX = "reservation:arrival:notice:order:";
|
|
|
+ private static final long ARRIVAL_REMINDER_REDIS_TTL_SECONDS = 30 * 60;
|
|
|
|
|
|
@Override
|
|
|
public Integer add(UserReservationDTO dto) {
|
|
|
@@ -1189,25 +1191,30 @@ public class UserReservationServiceImpl extends ServiceImpl<UserReservationMappe
|
|
|
if (orderIds == null || orderIds.isEmpty()) {
|
|
|
return 0;
|
|
|
}
|
|
|
- int sentCount = 0;
|
|
|
+ int smsSuccessCount = 0;
|
|
|
for (Integer orderId : orderIds) {
|
|
|
try {
|
|
|
- String redisKey = REDIS_KEY_ARRIVAL_REMINDER_PREFIX + orderId;
|
|
|
- if (baseRedisService.hasKey(redisKey)) {
|
|
|
+ String noticeKey = REDIS_KEY_ARRIVAL_NOTICE_PREFIX + orderId;
|
|
|
+ String smsKey = REDIS_KEY_ARRIVAL_SMS_PREFIX + orderId;
|
|
|
+ if (baseRedisService.hasKey(noticeKey) && baseRedisService.hasKey(smsKey)) {
|
|
|
continue;
|
|
|
}
|
|
|
if (sendArrivalReminderSmsForOrder(orderId)) {
|
|
|
- baseRedisService.setString(redisKey, "1", Long.valueOf(ARRIVAL_REMINDER_SENT_TTL_SECONDS));
|
|
|
- sentCount++;
|
|
|
+ smsSuccessCount++;
|
|
|
}
|
|
|
} catch (Exception e) {
|
|
|
- log.error("到店提醒短信处理异常,orderId={}", orderId, e);
|
|
|
+ log.error("到店提醒任务处理异常,orderId={}", orderId, e);
|
|
|
}
|
|
|
}
|
|
|
- log.info("到店提醒定时任务结束,符合条件订单数={}, 发送短信数={}", orderIds.size(), sentCount);
|
|
|
- return sentCount;
|
|
|
+ log.info("到店提醒定时任务结束,符合条件订单数={}, 短信成功数={}", orderIds.size(), smsSuccessCount);
|
|
|
+ return smsSuccessCount;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 单订单到店提醒:先写站内通知(与短信是否成功无关,各自 Redis 防重),再发短信。
|
|
|
+ *
|
|
|
+ * @return 本次是否短信发送成功(用于统计)
|
|
|
+ */
|
|
|
@Override
|
|
|
public boolean sendArrivalReminderSmsForOrder(Integer orderId) {
|
|
|
if (orderId == null) {
|
|
|
@@ -1229,40 +1236,57 @@ public class UserReservationServiceImpl extends ServiceImpl<UserReservationMappe
|
|
|
log.warn("到店提醒跳过:预约人电话为空,orderId={}", orderId);
|
|
|
return false;
|
|
|
}
|
|
|
- String startTimeStr = reservation.getStartTime() != null ? reservation.getStartTime().trim() : "";
|
|
|
- if (startTimeStr.isEmpty()) {
|
|
|
- startTimeStr = "未知时间";
|
|
|
+
|
|
|
+ String startTimeForSms = reservation.getStartTime() != null ? reservation.getStartTime().trim() : "";
|
|
|
+ if (startTimeForSms.isEmpty()) {
|
|
|
+ startTimeForSms = "未知时间";
|
|
|
}
|
|
|
StoreInfo storeInfo = storeInfoService.getById(reservation.getStoreId());
|
|
|
String storeName = storeInfo != null && storeInfo.getStoreName() != null ? storeInfo.getStoreName() : "未知店铺";
|
|
|
-
|
|
|
- LambdaQueryWrapper<UserReservationTable> tableWrapper = new LambdaQueryWrapper<>();
|
|
|
- tableWrapper.eq(UserReservationTable::getReservationId, reservation.getId())
|
|
|
- .eq(UserReservationTable::getDeleteFlag, 0)
|
|
|
- .orderByAsc(UserReservationTable::getSort);
|
|
|
- List<UserReservationTable> reservationTables = userReservationTableMapper.selectList(tableWrapper);
|
|
|
- String tableNumber = "未知桌号";
|
|
|
- if (reservationTables != null && !reservationTables.isEmpty()) {
|
|
|
- List<String> tableNumbers = reservationTables.stream()
|
|
|
- .map(rt -> {
|
|
|
- StoreBookingTable table = storeBookingTableService.getById(rt.getTableId());
|
|
|
- return table != null && table.getTableNumber() != null ? table.getTableNumber() : null;
|
|
|
- })
|
|
|
- .filter(tn -> tn != null && !tn.trim().isEmpty())
|
|
|
- .collect(Collectors.toList());
|
|
|
- if (!tableNumbers.isEmpty()) {
|
|
|
- tableNumber = String.join(",", tableNumbers);
|
|
|
+ String tableNumbers = resolveReservationTableNumbers(reservation.getId());
|
|
|
+
|
|
|
+ String noticeKey = REDIS_KEY_ARRIVAL_NOTICE_PREFIX + orderId;
|
|
|
+ if (!baseRedisService.hasKey(noticeKey)) {
|
|
|
+ boolean noticeOk = arrivalReminderNoticeService.sendArrivalReminderNotice(
|
|
|
+ phone, orderId, order.getId(), reservation.getId(),
|
|
|
+ reservation.getStartTime(), storeName, tableNumbers);
|
|
|
+ if (noticeOk) {
|
|
|
+ baseRedisService.setString(noticeKey, "1", Long.valueOf(ARRIVAL_REMINDER_REDIS_TTL_SECONDS));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- Integer smsResult = aliSms.sendArrivalReminderSms(phone, startTimeStr, storeName, tableNumber);
|
|
|
+ String smsKey = REDIS_KEY_ARRIVAL_SMS_PREFIX + orderId;
|
|
|
+ if (baseRedisService.hasKey(smsKey)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ Integer smsResult = aliSms.sendArrivalReminderSms(phone, startTimeForSms, storeName, tableNumbers);
|
|
|
if (smsResult != null && smsResult == 1) {
|
|
|
log.info("到店提醒短信发送成功,orderId={}, orderSn={}, phone={}", orderId, order.getOrderSn(), phone);
|
|
|
+ baseRedisService.setString(smsKey, "1", Long.valueOf(ARRIVAL_REMINDER_REDIS_TTL_SECONDS));
|
|
|
return true;
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
+ private String resolveReservationTableNumbers(Integer reservationId) {
|
|
|
+ LambdaQueryWrapper<UserReservationTable> w = new LambdaQueryWrapper<>();
|
|
|
+ w.eq(UserReservationTable::getReservationId, reservationId)
|
|
|
+ .eq(UserReservationTable::getDeleteFlag, 0)
|
|
|
+ .orderByAsc(UserReservationTable::getSort);
|
|
|
+ List<UserReservationTable> links = userReservationTableMapper.selectList(w);
|
|
|
+ if (links == null || links.isEmpty()) {
|
|
|
+ return "未知桌号";
|
|
|
+ }
|
|
|
+ List<String> nums = links.stream()
|
|
|
+ .map(rt -> {
|
|
|
+ StoreBookingTable t = storeBookingTableService.getById(rt.getTableId());
|
|
|
+ return t != null && t.getTableNumber() != null ? t.getTableNumber().trim() : null;
|
|
|
+ })
|
|
|
+ .filter(n -> n != null && !n.isEmpty())
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ return nums.isEmpty() ? "未知桌号" : String.join(",", nums);
|
|
|
+ }
|
|
|
+
|
|
|
/** 将页面 VO 字段复制到详情 VO,供前端订单详情页与 page 接口一致展示 */
|
|
|
private void copyPageVoToDetailVo(ReservationOrderPageVo page, ReservationOrderDetailVo detail) {
|
|
|
detail.setOrderId(page.getOrderId());
|