diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/SmHotelDownstreamPushLogController.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/SmHotelDownstreamPushLogController.java index bb4c24b3..997ccf65 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/SmHotelDownstreamPushLogController.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/SmHotelDownstreamPushLogController.java @@ -1,9 +1,12 @@ package com.bsd.pjcloud.supermarket.controller; +import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.bsd.pjcloud.common.core.util.R; +import com.bsd.pjcloud.common.log.annotation.SysLog; import com.bsd.pjcloud.supermarket.entity.SmHotelDownstreamPushLogEntity; import com.bsd.pjcloud.supermarket.service.SmHotelDownstreamPushLogService; +import com.bsd.pjcloud.supermarket.service.SmHotelMidService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; @@ -13,6 +16,8 @@ import org.springframework.http.HttpHeaders; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; +import java.util.Map; + @RestController @RequiredArgsConstructor @RequestMapping("/smHotelDownstreamPushLog") @@ -22,6 +27,8 @@ public class SmHotelDownstreamPushLogController { private final SmHotelDownstreamPushLogService pushLogService; + private final SmHotelMidService midService; + @Operation(summary = "分页") @GetMapping("/page") @PreAuthorize("@pms.hasPermission('supermarket_smHotelDownstreamPushLog_view')") @@ -36,4 +43,43 @@ public class SmHotelDownstreamPushLogController { public R detail(@PathVariable Long id) { return R.ok(pushLogService.getById(id)); } + + @SysLog("下游推送流水重试") + @Operation(summary = "按流水关联酒店重新推送到下游(整店:酒店/房型/产品/日历)") + @PostMapping("/retry/{id}") + @PreAuthorize("@pms.hasPermission('supermarket_smHotelDownstreamPushLog_retry')") + public R> retry(@PathVariable Long id) { + SmHotelDownstreamPushLogEntity row = pushLogService.getById(id); + if (row == null) { + return R.failed("流水不存在"); + } + if (row.getHotelId() == null) { + return R.failed("流水缺少酒店ID,无法重推"); + } + if (StrUtil.isBlank(row.getPartnerCode())) { + return R.failed("流水缺少下游伙伴,无法重推"); + } + String action = StrUtil.blankToDefault(row.getAction(), ""); + if ("order_query".equalsIgnoreCase(action) || "order_detail".equalsIgnoreCase(action)) { + return R.failed("订单查询类流水不支持从此处重推,请使用订单对接能力"); + } + return midService.pushToDownstream(row.getHotelId(), row.getPartnerCode().trim()); + } + + @SysLog("删除下游推送流水") + @Operation(summary = "删除单条推送流水") + @DeleteMapping("/{id}") + @PreAuthorize("@pms.hasPermission('supermarket_smHotelDownstreamPushLog_del')") + public R remove(@PathVariable Long id) { + return pushLogService.removeLog(id); + } + + @SysLog("按时间区间删除下游推送流水") + @Operation(summary = "按创建时间区间删除推送流水") + @PostMapping("/range") + @PreAuthorize("@pms.hasPermission('supermarket_smHotelDownstreamPushLog_del')") + public R removeRange(@RequestParam(required = false) String createTimeBegin, + @RequestParam(required = false) String createTimeEnd) { + return pushLogService.removeByCreateTimeRange(createTimeBegin, createTimeEnd); + } } diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/SmHotelDownstreamPushLogService.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/SmHotelDownstreamPushLogService.java index ee9c387f..14550f61 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/SmHotelDownstreamPushLogService.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/SmHotelDownstreamPushLogService.java @@ -2,6 +2,7 @@ package com.bsd.pjcloud.supermarket.service; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.IService; +import com.bsd.pjcloud.common.core.util.R; import com.bsd.pjcloud.supermarket.entity.SmHotelDownstreamPushLogEntity; public interface SmHotelDownstreamPushLogService extends IService { @@ -14,4 +15,10 @@ public interface SmHotelDownstreamPushLogService extends IService removeLog(Long id); + + /** 按创建时间区间删除(至少指定一端) */ + R removeByCreateTimeRange(String createTimeBegin, String createTimeEnd); } diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmHotelDownstreamPushLogServiceImpl.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmHotelDownstreamPushLogServiceImpl.java index 83ea6332..08af89af 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmHotelDownstreamPushLogServiceImpl.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmHotelDownstreamPushLogServiceImpl.java @@ -4,6 +4,7 @@ import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.bsd.pjcloud.common.core.util.R; import com.bsd.pjcloud.supermarket.entity.SmHotelDownstreamPushLogEntity; import com.bsd.pjcloud.supermarket.mapper.SmHotelDownstreamPushLogMapper; import com.bsd.pjcloud.supermarket.service.SmHotelDownstreamPushLogService; @@ -11,7 +12,9 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; @Slf4j @Service @@ -59,4 +62,62 @@ public class SmHotelDownstreamPushLogServiceImpl return baseMapper.delete(Wrappers.lambdaQuery() .lt(SmHotelDownstreamPushLogEntity::getCreateTime, before)); } + + @Override + public R removeLog(Long id) { + if (id == null) { + return R.failed("流水ID不能为空"); + } + SmHotelDownstreamPushLogEntity row = baseMapper.selectById(id); + if (row == null) { + return R.failed("流水不存在"); + } + baseMapper.deleteById(id); + return R.ok(Boolean.TRUE, "已删除"); + } + + @Override + public R removeByCreateTimeRange(String createTimeBegin, String createTimeEnd) { + LocalDateTime begin = parseDateTime(createTimeBegin, true); + LocalDateTime end = parseDateTime(createTimeEnd, false); + if (begin == null && end == null) { + return R.failed("请指定开始或结束时间"); + } + if (begin != null && end != null && end.isBefore(begin)) { + return R.failed("结束时间不能早于开始时间"); + } + var qw = Wrappers.lambdaQuery(); + if (begin != null) { + qw.ge(SmHotelDownstreamPushLogEntity::getCreateTime, begin); + } + if (end != null) { + qw.le(SmHotelDownstreamPushLogEntity::getCreateTime, end); + } + Long cnt = baseMapper.selectCount(qw); + int n = baseMapper.delete(qw); + log.info("|下游推送流水|按时间删除|begin={}|end={}|deleted={}|matched={}|", begin, end, n, cnt); + return R.ok(n, "已删除 " + n + " 条"); + } + + /** 支持 yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss;日期无时分秒时 begin=00:00:00、end=23:59:59 */ + private static LocalDateTime parseDateTime(String raw, boolean beginOfDay) { + if (StrUtil.isBlank(raw)) { + return null; + } + String s = raw.trim().replace('T', ' '); + try { + if (s.length() <= 10) { + LocalDate d = LocalDate.parse(s.substring(0, 10), DateTimeFormatter.ISO_LOCAL_DATE); + return beginOfDay ? d.atStartOfDay() : d.atTime(23, 59, 59); + } + if (s.length() == 16) { + return LocalDateTime.parse(s, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")); + } + return LocalDateTime.parse(s.substring(0, Math.min(19, s.length())), + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + } + catch (Exception e) { + return null; + } + } } diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmHotelMidServiceImpl.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmHotelMidServiceImpl.java index 75553103..759ccc06 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmHotelMidServiceImpl.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmHotelMidServiceImpl.java @@ -37,6 +37,8 @@ import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; /** @@ -56,6 +58,12 @@ public class SmHotelMidServiceImpl implements SmHotelMidService { /** 本地日历单次批量最长天数(含首尾) */ private static final int LOCAL_DAILY_BATCH_MAX_DAYS = 366; + /** + * 环启 cityName:从「省市区」串中取地级市短名(2~4 字 + 市),再去掉「市」。 + * 例:山东省青岛市城阳区 → 青岛;恩施市 → 恩施。 + */ + private static final Pattern HUANQI_CITY_PATTERN = Pattern.compile("([\\u4e00-\\u9fa5]{2,4})市"); + private final ShangyouHotelClient upstreamClient; private final ShangyouHotelProperties upstreamProperties; @@ -505,6 +513,8 @@ public class SmHotelMidServiceImpl implements SmHotelMidService { if (StrUtil.isBlank(cityName)) { cityName = StrUtil.trim(hotel.getDistrict()); } + // 环启要求只传市一级短名,且不要带「市」:恩施市 → 恩施 + cityName = normalizeHuanqiCityName(cityName); String address = StrUtil.trim(strObj(hotelBody.get("address"))); if (StrUtil.isBlank(address)) { address = StrUtil.trim(hotel.getAddress()); @@ -1833,6 +1843,34 @@ public class SmHotelMidServiceImpl implements SmHotelMidService { } } + /** + * 环启 cityName 规范化:只保留市一级短名,去掉后缀「市」。 + *
    + *
  • 山东省青岛市城阳区 → 青岛
  • + *
  • 恩施市 → 恩施
  • + *
  • 北京市朝阳区 → 北京
  • + *
+ */ + static String normalizeHuanqiCityName(String raw) { + if (StrUtil.isBlank(raw)) { + return ""; + } + String s = raw.trim().replace(" ", ""); + Matcher m = HUANQI_CITY_PATTERN.matcher(s); + String last = null; + while (m.find()) { + last = m.group(1); + } + if (StrUtil.isNotBlank(last)) { + return last; + } + // 已是「xx市」整串、或误写成带市后缀 + if (s.endsWith("市") && s.length() > 1) { + return s.substring(0, s.length() - 1); + } + return s; + } + private static class DailyDraft { private String productCode; private String roomTypeCode;