酒店优化
This commit is contained in:
1 parent
b9fd7f1294
commit
dfae8205a8
6 files changed
+132
-66
No files matched your search
+12
-12
@@ -7,28 +7,28 @@ import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Mapper
|
||||
public interface SmHotelDownstreamPushLogMapper extends PjcloudBaseMapper<SmHotelDownstreamPushLogEntity> {
|
||||
|
||||
/**
|
||||
* 仅取本页去重酒店 ID(走 sync_log_id+success+hotel_id 索引),避免整表 GROUP BY MAX。
|
||||
*/
|
||||
@Select("""
|
||||
SELECT DISTINCT hotel_id FROM canteen_hotel_downstream_push_log
|
||||
WHERE del_flag = '0' AND sync_log_id = #{syncLogId} AND success = '0' AND hotel_id IS NOT NULL
|
||||
ORDER BY hotel_id
|
||||
LIMIT #{offset}, #{limit}
|
||||
""")
|
||||
List<Long> pageDistinctFailedHotelIds(@Param("syncLogId") Long syncLogId, @Param("offset") long offset,
|
||||
@Param("limit") long limit);
|
||||
|
||||
@Select("""
|
||||
SELECT COUNT(DISTINCT hotel_id) FROM canteen_hotel_downstream_push_log
|
||||
WHERE del_flag = '0' AND sync_log_id = #{syncLogId} AND success = '0' AND hotel_id IS NOT NULL
|
||||
""")
|
||||
Long countFailedHotelsBySyncLogId(@Param("syncLogId") Long syncLogId);
|
||||
|
||||
@Select("""
|
||||
SELECT hotel_id AS hotelId, MAX(ext_hotel_id) AS extHotelId, MAX(error_msg) AS errorMsg
|
||||
FROM canteen_hotel_downstream_push_log
|
||||
WHERE del_flag = '0' AND sync_log_id = #{syncLogId} AND success = '0' AND hotel_id IS NOT NULL
|
||||
GROUP BY hotel_id
|
||||
ORDER BY hotel_id
|
||||
LIMIT #{offset}, #{limit}
|
||||
""")
|
||||
List<Map<String, Object>> pageFailedHotelsBySyncLogId(@Param("syncLogId") Long syncLogId,
|
||||
@Param("offset") long offset, @Param("limit") long limit);
|
||||
|
||||
@Select("""
|
||||
SELECT DISTINCT hotel_id FROM canteen_hotel_downstream_push_log
|
||||
WHERE del_flag = '0' AND sync_log_id = #{syncLogId} AND success = '0' AND hotel_id IS NOT NULL
|
||||
|
||||
+4
-6
@@ -31,15 +31,13 @@ public interface SmHotelDownstreamPushLogService extends IService<SmHotelDownstr
|
||||
*/
|
||||
List<SmHotelDownstreamGapExcel> exportGapsBySyncLogId(Long syncLogId);
|
||||
|
||||
/** 某次全量推中失败酒店数(按 hotel_id 去重,success=0) */
|
||||
/** 某次全量推中失败酒店数(按 hotel_id 去重,success=0);仅兜底,优先用同步日志 failCount */
|
||||
long countFailedHotelsBySyncLogId(Long syncLogId);
|
||||
|
||||
/**
|
||||
* 分页查某次全量推失败酒店(按 hotel_id 去重)。
|
||||
* @return 每行含 hotelId / extHotelId / errorMsg
|
||||
*/
|
||||
Page<java.util.Map<String, Object>> pageFailedHotelsBySyncLogId(Page<?> page, Long syncLogId);
|
||||
/** 分页取失败酒店 ID(DISTINCT + LIMIT,不做全量 GROUP BY) */
|
||||
List<Long> pageDistinctFailedHotelIds(Long syncLogId, long offset, long limit);
|
||||
|
||||
/** 某次全量推全部失败酒店 ID(供只重试失败) */
|
||||
List<Long> listFailedHotelIdsBySyncLogId(Long syncLogId);
|
||||
|
||||
}
|
||||
+2
-1
@@ -134,7 +134,8 @@ public interface SmHotelOrderService {
|
||||
R<Boolean> bookUpstreamForExisting(Long orderId);
|
||||
|
||||
/**
|
||||
* 下游销售前二次校验(查本地酒店/产品/上架/日历库存/可选验价)。
|
||||
* 下游销售前二次校验:本地酒店/产品/上架/日历库存/价差;
|
||||
* 非本地(如飞猪/商游)会同步调上游 checkAvailability,预检阶段即拦不可订。
|
||||
* 入参建议:extHotelId、extProductId、checkIn、checkOut、roomCnt、price(可选)。
|
||||
*/
|
||||
R<Map<String, Object>> checkBookableForDownstream(Map<String, Object> body);
|
||||
|
||||
+5
-16
@@ -340,23 +340,12 @@ public class SmHotelDownstreamPushLogServiceImpl
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<Map<String, Object>> pageFailedHotelsBySyncLogId(Page<?> page, Long syncLogId) {
|
||||
Page<Map<String, Object>> out = new Page<>(page.getCurrent(), page.getSize());
|
||||
if (syncLogId == null) {
|
||||
out.setTotal(0);
|
||||
out.setRecords(List.of());
|
||||
return out;
|
||||
public List<Long> pageDistinctFailedHotelIds(Long syncLogId, long offset, long limit) {
|
||||
if (syncLogId == null || limit <= 0) {
|
||||
return List.of();
|
||||
}
|
||||
long total = countFailedHotelsBySyncLogId(syncLogId);
|
||||
out.setTotal(total);
|
||||
if (total <= 0) {
|
||||
out.setRecords(List.of());
|
||||
return out;
|
||||
}
|
||||
long offset = Math.max(0L, (page.getCurrent() - 1) * page.getSize());
|
||||
List<Map<String, Object>> rows = baseMapper.pageFailedHotelsBySyncLogId(syncLogId, offset, page.getSize());
|
||||
out.setRecords(rows == null ? List.of() : rows);
|
||||
return out;
|
||||
List<Long> ids = baseMapper.pageDistinctFailedHotelIds(syncLogId, Math.max(0L, offset), limit);
|
||||
return ids == null ? List.of() : ids;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+55
-3
@@ -1104,17 +1104,69 @@ public class SmHotelOrderServiceImpl extends ServiceImpl<SmHotelOrderMapper, SmH
|
||||
}
|
||||
}
|
||||
|
||||
// 第三方来源:额外提示上游可定需在真正下单时再 DoubleCheck
|
||||
data.put("needUpstreamDoubleCheck", !localDs);
|
||||
// 非本地(飞猪/商游等):预检阶段就必须打上游可订,避免校验过了下单才发现满房
|
||||
boolean upstreamChecked = false;
|
||||
if (!localDs) {
|
||||
String upstreamMsg = checkUpstreamAvailabilityForDownstream(hotel, product, checkIn, checkOut, roomCnt);
|
||||
if (upstreamMsg != null) {
|
||||
reasons.add(upstreamMsg);
|
||||
}
|
||||
else {
|
||||
upstreamChecked = true;
|
||||
}
|
||||
}
|
||||
data.put("upstreamAvailChecked", upstreamChecked);
|
||||
data.put("needUpstreamDoubleCheck", !localDs && !upstreamChecked);
|
||||
data.put("bookable", reasons.isEmpty());
|
||||
data.put("reasons", reasons);
|
||||
// 仅价差:圣诺等可落单待人工确认,其它硬伤仍拒单
|
||||
// 仅价差:圣诺等可落单待人工确认,其它硬伤仍拒单(上游不可订不算价差)
|
||||
boolean priceMismatchOnly = !reasons.isEmpty() && reasons.stream()
|
||||
.allMatch(r -> r != null && String.valueOf(r).contains("结算价不一致"));
|
||||
data.put("priceMismatchOnly", priceMismatchOnly);
|
||||
return R.ok(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下游预检专用:调上游 checkAvailability。
|
||||
* @return null=可订;非空=不可订/异常原因(写入 reasons)
|
||||
*/
|
||||
private String checkUpstreamAvailabilityForDownstream(SmHotelEntity hotel, SmHotelProductEntity product,
|
||||
LocalDate checkIn, LocalDate checkOut, int roomCnt) {
|
||||
if (hotel == null || product == null || checkIn == null || checkOut == null) {
|
||||
return "上游可订校验参数不完整";
|
||||
}
|
||||
if (upstreamGatewayRegistry.isUnsupportedChannel(hotel.getSheetName())) {
|
||||
return "未接入的上游渠道,无法校验可订:" + upstreamGatewayRegistry.resolveChannelKey(hotel.getSheetName());
|
||||
}
|
||||
Optional<HotelUpstreamOrderGateway> gatewayOpt = upstreamGatewayRegistry.find(hotel.getSheetName());
|
||||
if (gatewayOpt.isEmpty()) {
|
||||
return "未找到上游订单网关,无法校验可订";
|
||||
}
|
||||
HotelUpstreamOrderGateway gateway = gatewayOpt.get();
|
||||
com.alibaba.fastjson.JSONObject upstreamBody = new com.alibaba.fastjson.JSONObject();
|
||||
upstreamBody.put("hotelId", hotel.getHotelCode());
|
||||
upstreamBody.put("roomTypeId", StrUtil.blankToDefault(product.getRoomTypeCode(), ""));
|
||||
upstreamBody.put("rateCode", StrUtil.blankToDefault(product.getPriceCode(), ""));
|
||||
upstreamBody.put("checkInDate", checkIn.toString());
|
||||
upstreamBody.put("checkOutDate", checkOut.toString());
|
||||
upstreamBody.put("guaranteeMode",
|
||||
StrUtil.blankToDefault(channelConfigService.resolveShangyouRuntime().getOrderGuaranteeMode(), "NONE"));
|
||||
upstreamBody.put("isGroup", false);
|
||||
if (StrUtil.isBlank(upstreamBody.getString("hotelId")) || StrUtil.isBlank(upstreamBody.getString("rateCode"))) {
|
||||
return "上游可订校验缺少 hotelId/rateCode(请检查本地酒店编码与产品价格码)";
|
||||
}
|
||||
SmHotelOrderEntity probe = new SmHotelOrderEntity();
|
||||
probe.setId(null);
|
||||
try {
|
||||
return doubleCheckAvailability(gateway, probe, upstreamBody, Math.max(1, roomCnt));
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.warn("|下游预检|上游可订异常|hotel={}|product={}|{}~{}|err={}|", hotel.getHotelCode(),
|
||||
product.getProductCode(), checkIn, checkOut, e.getMessage());
|
||||
return "上游可订校验失败:" + StrUtil.blankToDefault(e.getMessage(), e.getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<JSONObject> createByAdmin(Map<String, Object> body) {
|
||||
|
||||
+54
-28
@@ -11,6 +11,7 @@ import com.bsd.pjcloud.supermarket.constant.SmHotelDownstreamProtocol;
|
||||
import com.bsd.pjcloud.supermarket.dto.SmHotelLocalDailyBatchDTO;
|
||||
import com.bsd.pjcloud.supermarket.dto.SmHotelSyncFailExcel;
|
||||
import com.bsd.pjcloud.supermarket.dto.SmHotelSyncFailLineVO;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelDownstreamPushLogEntity;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelEntity;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelSyncLogEntity;
|
||||
import com.bsd.pjcloud.supermarket.integration.downstream.DownstreamPushLogContext;
|
||||
@@ -641,36 +642,35 @@ public class SmHotelSyncServiceImpl implements SmHotelSyncService {
|
||||
long size = out.getSize() <= 0 ? 20 : out.getSize();
|
||||
|
||||
if (TYPE_DOWNSTREAM_PUSH.equals(e.getSyncType())) {
|
||||
long pushFailHotels = downstreamPushLogService.countFailedHotelsBySyncLogId(id);
|
||||
if (pushFailHotels > 0) {
|
||||
Page<Map<String, Object>> raw = downstreamPushLogService.pageFailedHotelsBySyncLogId(
|
||||
new Page<>(current, size), id);
|
||||
out.setTotal(raw.getTotal());
|
||||
List<Map<String, Object>> maps = raw.getRecords();
|
||||
List<Long> ids = new ArrayList<>();
|
||||
if (CollUtil.isNotEmpty(maps)) {
|
||||
for (Map<String, Object> m : maps) {
|
||||
Long hid = toLong(m.get("hotelId"));
|
||||
if (hid != null) {
|
||||
ids.add(hid);
|
||||
}
|
||||
}
|
||||
// 总数优先用同步日志 failCount,避免每次 COUNT(DISTINCT) 扫几十万流水
|
||||
long totalHint = e.getFailCount() == null ? -1L : e.getFailCount().longValue();
|
||||
long offset = Math.max(0L, (current - 1) * size);
|
||||
List<Long> pageHotelIds = downstreamPushLogService.pageDistinctFailedHotelIds(id, offset, size);
|
||||
if (CollUtil.isNotEmpty(pageHotelIds) || totalHint > 0) {
|
||||
if (totalHint < 0) {
|
||||
totalHint = downstreamPushLogService.countFailedHotelsBySyncLogId(id);
|
||||
}
|
||||
Map<Long, SmHotelEntity> hotelMap = loadHotelMapByIds(ids);
|
||||
List<SmHotelSyncFailLineVO> rows = new ArrayList<>();
|
||||
if (CollUtil.isNotEmpty(maps)) {
|
||||
for (Map<String, Object> m : maps) {
|
||||
Long hid = toLong(m.get("hotelId"));
|
||||
SmHotelEntity h = hid == null ? null : hotelMap.get(hid);
|
||||
SmHotelSyncFailLineVO vo = new SmHotelSyncFailLineVO();
|
||||
vo.setHotelId(hid);
|
||||
vo.setHotelCode(h != null ? StrUtil.blankToDefault(h.getHotelCode(), String.valueOf(hid))
|
||||
: (hid == null ? "" : String.valueOf(hid)));
|
||||
vo.setHotelName(h != null ? StrUtil.blankToDefault(h.getHotelName(), "-") : "-");
|
||||
vo.setReason(StrUtil.blankToDefault(str(m.get("errorMsg")), "推送失败"));
|
||||
vo.setRawLine(vo.getHotelCode() + "|" + vo.getHotelName() + "|" + vo.getReason());
|
||||
rows.add(vo);
|
||||
out.setTotal(Math.max(totalHint, 0L));
|
||||
if (CollUtil.isEmpty(pageHotelIds)) {
|
||||
out.setRecords(List.of());
|
||||
return out;
|
||||
}
|
||||
Map<Long, SmHotelEntity> hotelMap = loadHotelMapByIds(pageHotelIds);
|
||||
Map<Long, String> reasonMap = loadLatestFailReasonByHotelIds(id, pageHotelIds);
|
||||
List<SmHotelSyncFailLineVO> rows = new ArrayList<>(pageHotelIds.size());
|
||||
for (Long hid : pageHotelIds) {
|
||||
if (hid == null) {
|
||||
continue;
|
||||
}
|
||||
SmHotelEntity h = hotelMap.get(hid);
|
||||
SmHotelSyncFailLineVO vo = new SmHotelSyncFailLineVO();
|
||||
vo.setHotelId(hid);
|
||||
vo.setHotelCode(h != null ? StrUtil.blankToDefault(h.getHotelCode(), String.valueOf(hid))
|
||||
: String.valueOf(hid));
|
||||
vo.setHotelName(h != null ? StrUtil.blankToDefault(h.getHotelName(), "-") : "-");
|
||||
vo.setReason(StrUtil.blankToDefault(reasonMap.get(hid), "推送失败"));
|
||||
vo.setRawLine(vo.getHotelCode() + "|" + vo.getHotelName() + "|" + vo.getReason());
|
||||
rows.add(vo);
|
||||
}
|
||||
out.setRecords(rows);
|
||||
return out;
|
||||
@@ -733,6 +733,32 @@ public class SmHotelSyncServiceImpl implements SmHotelSyncService {
|
||||
return map;
|
||||
}
|
||||
|
||||
/** 本页失败酒店的最新失败原因(只查这批 hotelId,不扫全量) */
|
||||
private Map<Long, String> loadLatestFailReasonByHotelIds(Long syncLogId, List<Long> hotelIds) {
|
||||
Map<Long, String> map = new HashMap<>();
|
||||
if (syncLogId == null || CollUtil.isEmpty(hotelIds)) {
|
||||
return map;
|
||||
}
|
||||
List<SmHotelDownstreamPushLogEntity> rows = downstreamPushLogService.list(Wrappers
|
||||
.<SmHotelDownstreamPushLogEntity>lambdaQuery()
|
||||
.select(SmHotelDownstreamPushLogEntity::getId, SmHotelDownstreamPushLogEntity::getHotelId,
|
||||
SmHotelDownstreamPushLogEntity::getErrorMsg)
|
||||
.eq(SmHotelDownstreamPushLogEntity::getSyncLogId, syncLogId)
|
||||
.eq(SmHotelDownstreamPushLogEntity::getSuccess, "0")
|
||||
.in(SmHotelDownstreamPushLogEntity::getHotelId, hotelIds)
|
||||
.orderByDesc(SmHotelDownstreamPushLogEntity::getId));
|
||||
if (CollUtil.isEmpty(rows)) {
|
||||
return map;
|
||||
}
|
||||
for (SmHotelDownstreamPushLogEntity row : rows) {
|
||||
if (row == null || row.getHotelId() == null || map.containsKey(row.getHotelId())) {
|
||||
continue;
|
||||
}
|
||||
map.put(row.getHotelId(), row.getErrorMsg());
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private static Long toLong(Object v) {
|
||||
if (v == null) {
|
||||
return null;
|
||||
|
||||
Reference in new issue
Block a user