砍价活动 时间槽 修正新需求。 增加必要的字段

This commit is contained in:
Jack 2025-11-06 12:38:06 +08:00
parent 0c15b2c034
commit 38b674e05a
7 changed files with 158 additions and 173 deletions

View File

@ -56,6 +56,9 @@ public class ShopActivityCutprice implements Serializable {
@ApiModelProperty(value = "砍价人数")
private Integer ac_num;
@ApiModelProperty(value = "砍价过期时间戳")
private Long expired_at;
@Version
@ApiModelProperty(value = "乐观锁")
private Integer version;

View File

@ -127,4 +127,12 @@ public class ShopStoreActivityBase implements Serializable {
@TableField(updateStrategy = NOT_EMPTY)
private String flow_no;
@Version
@ApiModelProperty(value = "在活动时间范围内,用户砍价允许的天数")
private Integer cut_days;
@Version
@ApiModelProperty(value = "活动商品的总数量")
private Integer product_cont;
}

View File

@ -439,15 +439,15 @@ public class DateTimeUtils {
* @param startTimeStr 开始时间字符串支持格式如 HH:mm, HH:mm:ss, HH:mm:ss.SSS
* @param endTimeStr 结束时间字符串支持格式如 HH:mm, HH:mm:ss, HH:mm:ss.SSS
* @param currentTime 要判断的时间点
* @return 如果在时间段内返回true否则返回false出现异常时返回false不影响主流程
* @return 返回值: -1-在时间段之前 0-时间段内1-在时间段之后
*/
public static boolean isTimeInRange(String startTimeStr, String endTimeStr, LocalDateTime currentTime) {
public static int isTimeInRange(String startTimeStr, String endTimeStr, LocalDateTime currentTime) {
try {
// 参数校验
if (startTimeStr == null || endTimeStr == null || currentTime == null) {
log.warn("时间参数不能为空startTimeStr: {}, endTimeStr: {}, currentTime: {}",
startTimeStr, endTimeStr, currentTime);
return false;
return -1;
}
// 解析开始时间 - 支持多种时间格式
@ -459,37 +459,53 @@ public class DateTimeUtils {
// 获取当前时间的时间部分
LocalTime nowTime = currentTime.toLocalTime();
// 处理开始时间等于结束时间的情况
if (startTime.equals(endTime)) {
return nowTime.equals(startTime) ? 0 : -1; // 精确匹配为时间段内否则为时间段前
}
// 处理跨天情况例如 22:00 - 06:00
if (endTime.isBefore(startTime)) {
// 如果结束时间小于开始时间说明跨越了午夜
// 当前时间需要满足大于等于开始时间 或者 小于等于结束时间
return !nowTime.isBefore(startTime) || !nowTime.isAfter(endTime);
// 跨天时间段: [startTime, 24:00) [00:00, endTime]
if (!nowTime.isBefore(startTime) || !nowTime.isAfter(endTime)) {
return 0; // 时间段内
} else {
// 对于跨天情况不在时间段内的唯一可能就是"时间段前"
// 因为跨天时间段覆盖了从startTime到endTime经过午夜的整个区间
return -1; // 时间段前
}
} else {
// 正常情况不跨天当前时间需要在开始时间和结束时间之间包含边界
return !nowTime.isBefore(startTime) && !nowTime.isAfter(endTime);
if (nowTime.isBefore(startTime)) {
return -1; // 时间段前
} else if (nowTime.isAfter(endTime)) {
return 1; // 时间段后
} else {
return 0; // 时间段内
}
}
} catch (Exception e) {
// 捕获解析异常记录日志并返回false避免影响主流程
log.error("判断时间是否在范围内时发生异常startTimeStr: {}, endTimeStr: {}, currentTime: {}",
startTimeStr, endTimeStr, currentTime, e);
return false;
return -1;
}
}
/**
* 判断指定时间是否在两个时间点之间包含边界
*
* @param startTimeStr 开始时间字符串支持格式如 HH:mm, HH:mm:ss, HH:mm:ss.SSS
* @param endTimeStr 结束时间字符串支持格式如 HH:mm, HH:mm:ss, HH:mm:ss.SSS
* @param currentTime 要判断的时间点
* @return 如果在时间段内返回true否则返回false出现异常时返回false不影响主流程
* @return 返回值: -1-在时间段之前 0-时间段内1-在时间段之后
*/
public static boolean isTimeInRange(String startTimeStr, String endTimeStr, Date currentTime) {
public static int isTimeInRange(String startTimeStr, String endTimeStr, Date currentTime) {
if (currentTime == null) {
log.warn("时间参数不能为空startTimeStr: {}, endTimeStr: {}, currentTime: null",
startTimeStr, endTimeStr);
return false;
return -1;
}
// 将Date转换为LocalDateTime并调用已有的方法
LocalDateTime localDateTime = currentTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
@ -501,9 +517,9 @@ public class DateTimeUtils {
*
* @param startTimeStr 开始时间字符串支持格式如 HH:mm, HH:mm:ss, HH:mm:ss.SSS
* @param endTimeStr 结束时间字符串支持格式如 HH:mm, HH:mm:ss, HH:mm:ss.SSS
* @return 如果在时间段内返回true否则返回false
* @return 返回值: -1-在时间段之前 0-时间段内1-在时间段之后
*/
public static boolean isCurrentTimeInRange(String startTimeStr, String endTimeStr) {
public static int isCurrentTimeInRange(String startTimeStr, String endTimeStr) {
return isTimeInRange(startTimeStr, endTimeStr, LocalDateTime.now());
}

View File

@ -902,7 +902,7 @@ public class ShopOrderInfoServiceImpl extends BaseServiceImpl<ShopOrderInfoMappe
}
// 6. 店铺营业时间检查
if (!DateTimeUtils.isTimeInRange(shopStoreInfo.getStore_opening_hours(), shopStoreInfo.getStore_close_hours(), bookingBeginTime)) {
if (DateTimeUtils.isTimeInRange(shopStoreInfo.getStore_opening_hours(), shopStoreInfo.getStore_close_hours(), bookingBeginTime) != 0) {
String message = StrUtil.format("[预约单校验] 请在 {}-{} 店铺营业时间内预约下单", shopStoreInfo.getStore_opening_hours(), shopStoreInfo.getStore_close_hours());
return Pair.of(false, message);
}
@ -1035,110 +1035,66 @@ public class ShopOrderInfoServiceImpl extends BaseServiceImpl<ShopOrderInfoMappe
return Collections.emptyList();
}
// 显示几天的时间槽默认7天
int daysCnt = 7;
List<BookingArgDTO> result = new ArrayList<>();
// 判断今天还能不能立即下单和预约下单就有今天的时间槽不能就没有今天的时间槽
boolean canBookingToday = DateTimeUtils.isTimeInRange(timesPair.getFirst(), timesPair.getSecond(), new Date());
//-1-在时间段之前 0-时间段内1-在时间段之后
int inRangeVal = DateTimeUtils.isCurrentTimeInRange(timesPair.getFirst(), timesPair.getSecond());
// 生成7天的可预约时间槽数据
Date startDate = new Date();
if (!canBookingToday) {
// 如果今天不能下单和预约则从明天开始生成时间槽
startDate = DateUtil.offsetDay(new Date(), 1);
}
// 确定起始日期
Date startDate = inRangeVal == 1 ? DateUtil.offsetDay(new Date(), 1) : new Date();
boolean isTodayAvailable = inRangeVal <= 0; // 今天是否可预约在营业时间之前或之中
for (int i = 0; i < daysCnt; i++) {
startDate = DateUtil.offsetDay(startDate, i);
Date currentDate = DateUtil.offsetDay(startDate, i);
BookingArgDTO bookingArgDTO = new BookingArgDTO();
// 设置日期相关信息
String dateStr = DateUtil.format(startDate, "yyyy-MM-dd");
String displayDateStr = DateUtil.format(startDate, "MM月dd日");
String dateStr = DateUtil.format(currentDate, "yyyy-MM-dd");
String displayDateStr = DateUtil.format(currentDate, "MM月dd日");
// 安全获取星期信息
String weekStr = "星期";
try {
weekStr = DateTimeUtils.getWeekOfDate(startDate);
weekStr = DateTimeUtils.getWeekOfDate(currentDate);
} catch (Exception e) {
logger.warn("[生成预约参数] 获取星期信息异常使用默认值date: {}", dateStr);
}
// 设置date_title - 优化后的代码
// 设置date_title
String dateTitle;
if (i == 0) {
dateTitle = canBookingToday ? "今天" : "明天";
dateTitle = isTodayAvailable ? "今天" : "明天";
} else if (i == 1) {
dateTitle = canBookingToday ? "明天" : "";
dateTitle = isTodayAvailable ? "后天" : "";
} else {
dateTitle = displayDateStr;
}
bookingArgDTO.setDate_title(dateTitle + "" + weekStr + "");
bookingArgDTO.setDate_title(dateTitle + "" + weekStr + "");
bookingArgDTO.setDate_str(displayDateStr);
bookingArgDTO.setDate(dateStr);
// 生成时间项
// List<BookingArgDTO.BookingArgItem> items = new ArrayList<>();
// 如果是今天且在配送时间内添加"立即送出"选项
// if (i == 0 && canBookingToday) {
// BookingArgDTO.BookingArgItem immediateItem = new BookingArgDTO.BookingArgItem();
// immediateItem.setTime_title("立即送出");
// immediateItem.setBooking_at(0L);
// immediateItem.setBooking_state(1);
// immediateItem.setBooking_begin_time("");
// immediateItem.setBooking_end_time("");
// items.add(immediateItem);
// }
String startTimeStr = timesPair.getFirst();
String endTimeStr = timesPair.getSecond();
boolean isToday = i == 0 && canBookingToday;
// 生成时间项
List<BookingArgDTO.BookingArgItem> items = generateTimeSlots(dateStr, startTimeStr, endTimeStr, isToday);
// if (i == 0 && canBookingToday) {
// items.addAll(timeSlots.stream().filter(item -> item.getBooking_state() != 1).collect(Collectors.toList()));
// } else {
// items.addAll(timeSlots);
// }
bookingArgDTO.setWorking_hours(String.format("%s-%s", startTimeStr, endTimeStr));
bookingArgDTO.setItems(items);
result.add(bookingArgDTO);
}
logger.debug("[生成预约参数] 成功生成预约参数storeId: {}, timesMap: {}, 参数数量: {}",
storeIds, timesPair, result.size());
return result;
}
/**
* 生成时间槽列表
*
* @param dateStr 日期字符串 yyyy-MM-dd
* @param openingHours 开始营业时间 HH:mm
* @param closeHours 结束营业时间 HH:mm
* @param isToday 是否是今天
* @return 时间槽列表
*/
private List<BookingArgDTO.BookingArgItem> generateTimeSlots(String dateStr, String openingHours, String closeHours, boolean isToday) {
// 参数校验
if (StrUtil.isBlank(dateStr) || StrUtil.isBlank(openingHours) || StrUtil.isBlank(closeHours)) {
logger.warn("[生成时间槽] 参数为空dateStr: {}, openingHours: {}, closeHours: {}", dateStr, openingHours, closeHours);
return Collections.emptyList();
}
try {
// 生成时间槽
List<BookingArgDTO.BookingArgItem> items = new ArrayList<>();
// 添加"立即送出"选项仅限今天
if (isToday) {
// 参数校验
if (StrUtil.isNotBlank(dateStr) && StrUtil.isNotBlank(startTimeStr) && StrUtil.isNotBlank(endTimeStr)) {
try {
// 解析营业时间
Date openTime = DateUtil.parse(dateStr + " " + startTimeStr + ":00", "yyyy-MM-dd HH:mm:ss");
Date closeTime = DateUtil.parse(dateStr + " " + endTimeStr + ":00", "yyyy-MM-dd HH:mm:ss");
// 验证营业时间有效性
if (!openTime.after(closeTime)) {
// 在营业时间段内且是今天添加"立即送出"选项
if (i == 0 && isTodayAvailable && inRangeVal == 0) {
BookingArgDTO.BookingArgItem immediateItem = new BookingArgDTO.BookingArgItem();
immediateItem.setTime_title("立即送出");
immediateItem.setBooking_at(0L);
@ -1148,43 +1104,34 @@ public class ShopOrderInfoServiceImpl extends BaseServiceImpl<ShopOrderInfoMappe
items.add(immediateItem);
}
// 解析营业时间
Date openTime = DateUtil.parse(dateStr + " " + openingHours + ":00", "yyyy-MM-dd HH:mm:ss");
Date closeTime = DateUtil.parse(dateStr + " " + closeHours + ":00", "yyyy-MM-dd HH:mm:ss");
// 验证营业时间有效性
if (openTime.after(closeTime)) {
logger.warn("[生成时间槽] 营业时间无效openTime: {}, closeTime: {}", openTime, closeTime);
return items;
}
// 时间槽间隔分钟
int slotInterval = 15;
// 对于今天需要特殊处理第二个时间槽从当前时间+50分钟开始
// 确定时间槽开始时间
Date startTime = openTime;
if (isToday) {
// 当前时间+50分钟作为第二个时间槽的开始时间
if (i == 0 && isTodayAvailable && inRangeVal <= 0) {
// 当前时间+50分钟作为时间槽的开始时间
Date nowPlusFifty = DateUtil.offsetMinute(new Date(), CommonConstant.MIN_DELAY_MINUTES_FOR_BOOKING_ORDER);
// 确保开始时间在营业时间范围内
if (nowPlusFifty.after(openTime)) {
if (nowPlusFifty.before(closeTime)) {
// 如果当前时间+50分钟在营业时间范围内则从该时间开始
if (nowPlusFifty.after(openTime) && nowPlusFifty.before(closeTime)) {
startTime = nowPlusFifty;
}
logger.debug("[生成时间槽] 当前时间+50分钟在营业时间范围内则从该时间开始");
} else {
// 如果当前时间+50分钟已经超过营业结束时间则不生成后续时间槽
else if (nowPlusFifty.after(closeTime)) {
logger.debug("[生成时间槽] 当前时间+50分钟已超过营业结束时间不生成后续时间槽");
return items;
startTime = null; // 标记为不生成时间槽
}
}
}
// 生成时间槽
if (startTime != null) {
Date currentTimeSlot = startTime;
int slotCount = 0;
final int MAX_SLOTS = 48; // 最多生成48个时间段防止异常循环
final int MAX_SLOTS = 96; // 最多生成96个时间段防止异常循环
while (currentTimeSlot.before(closeTime) && slotCount < MAX_SLOTS) {
Date endTimeSlot = DateUtil.offsetMinute(currentTimeSlot, slotInterval);
Date endTimeSlot = DateUtil.offsetMinute(currentTimeSlot, 15);
// 如果结束时间超过了营业结束时间则使用营业结束时间
if (endTimeSlot.after(closeTime)) {
@ -1194,9 +1141,9 @@ public class ShopOrderInfoServiceImpl extends BaseServiceImpl<ShopOrderInfoMappe
// 创建时间段项
BookingArgDTO.BookingArgItem timeItem = new BookingArgDTO.BookingArgItem();
String beginTimeStr = DateUtil.format(currentTimeSlot, "HH:mm");
String endTimeStr = DateUtil.format(endTimeSlot, "HH:mm");
String endTimeStrItem = DateUtil.format(endTimeSlot, "HH:mm");
timeItem.setTime_title(beginTimeStr + "-" + endTimeStr);
timeItem.setTime_title(beginTimeStr + "-" + endTimeStrItem);
// 时间戳计算
long bookingAt = currentTimeSlot.getTime() / 1000;
@ -1222,15 +1169,25 @@ public class ShopOrderInfoServiceImpl extends BaseServiceImpl<ShopOrderInfoMappe
if (slotCount >= MAX_SLOTS) {
logger.warn("[生成时间槽] 时间段数量超过最大限制date: {}", dateStr);
}
logger.debug("[生成时间槽] 成功生成时间槽date: {}, 时间段数量: {}", dateStr, items.size());
return items;
}
} else {
logger.warn("[生成时间槽] 营业时间无效openTime: {}, closeTime: {}", openTime, closeTime);
}
} catch (Exception e) {
logger.error("[生成时间槽] 生成时间槽异常date: {}, openingHours: {}, closeHours: {}", dateStr, openingHours, closeHours, e);
return Collections.emptyList();
logger.error("[生成时间槽] 生成时间槽异常date: {}, openingHours: {}, closeHours: {}", dateStr, startTimeStr, endTimeStr, e);
}
}
bookingArgDTO.setItems(items);
result.add(bookingArgDTO);
}
logger.debug("[生成预约参数] 成功生成预约参数storeId: {}, timesMap: {}, 参数数量: {}",
storeIds, timesPair, result.size());
return result;
}
/**
* 根据 storeIds一个或多个 storeid 34,23,43,23,先对id去重
* 再获取多个店铺的营业时间 List<map{startTimeStr, endTimeStr}> 列表list

View File

@ -4217,7 +4217,7 @@ public class ShopStoreBaseServiceImpl extends BaseServiceImpl<ShopStoreBaseMappe
// 检查店铺是否营业中且营业时间已设置
if (CommonConstant.Enable.equals(storeBizState) && !StrUtil.hasBlank(openingHours, closingHours)) {
// 检查当前时间是否在营业时间内
if (!DateTimeUtils.isCurrentTimeInRange(openingHours, closingHours)) {
if (DateTimeUtils.isCurrentTimeInRange(openingHours, closingHours) != 0) {
// 不在营业时间内返回已打烊状态
return CommonConstant.Disable2;
}
@ -4265,7 +4265,7 @@ public class ShopStoreBaseServiceImpl extends BaseServiceImpl<ShopStoreBaseMappe
&& StrUtil.isNotBlank(openingHours)
&& StrUtil.isNotBlank(closingHours)) {
// 检查当前时间是否在营业时间内
if (!DateTimeUtils.isCurrentTimeInRange(openingHours, closingHours)) {
if (DateTimeUtils.isCurrentTimeInRange(openingHours, closingHours) != 0) {
// 不在营业时间内返回已打烊状态
log.debug("店铺当前不在营业时间内storeId: {}, openingHours: {}, closingHours: {}",
storeId, openingHours, closingHours);

View File

@ -4,7 +4,7 @@
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
ac_id, activity_id, user_id, ac_sale_price, ac_datetime, order_id, ac_mix_limit_price, ac_num
ac_id, activity_id, user_id, ac_sale_price, ac_datetime, order_id, ac_mix_limit_price, ac_num, expired_at, version
</sql>
</mapper>

View File

@ -6,7 +6,8 @@
<sql id="Base_Column_List">
activity_id, store_id, user_id, activity_name, activity_title, activity_remark, activity_combo_id,
activity_type_id, activity_starttime, activity_endtime, activity_state, activity_rule, activity_type,
activity_order, activity_is_finish, item_id, subsite_id
activity_order, activity_is_finish, item_id, subsite_id,activity_use_level, activity_releasetime,
activity_on_is_off, activity_share_num, version, flow_no, cut_days, product_cont
</sql>
</mapper>