This commit is contained in:
zjf committed 2026-10-28 15:44:45 +08:00
1 parent c02a897876
commit 1ee75e9104
2 files changed
+86 -8

No files matched your search

@@ -33,7 +33,7 @@ public class SmHotelDownstreamPushLogEntity extends Model<SmHotelDownstreamPushL
@Schema(description = "外部酒店ID")
private String extHotelId;
@Schema(description = "动作:hotel_sync/apartment_sync/product_sync/daily_sync/push_skip")
@Schema(description = "动作:hotel_sync/apartment_sync/product_sync/daily_sync/push_skip/push_partial")
private String action;
@Schema(description = "完整请求URL")
@@ -51,7 +51,7 @@ public class SmHotelDownstreamPushLogEntity extends Model<SmHotelDownstreamPushL
@Schema(description = "业务code")
private Integer bizCode;
@Schema(description = "1成功 0失败 2跳过(下架/无产品/无日历等预检)")
@Schema(description = "1成功 0失败 2跳过/部分未推上(预检跳过或 push_partial)")
private String success;
@Schema(description = "失败摘要")
@@ -653,19 +653,41 @@ public class SmHotelMidServiceImpl implements SmHotelMidService {
cn.hutool.json.JSONArray nsRooms = infoData.getJSONArray("rooms");
Map<String, Long> roomTypeToNs = matchShengnuoRooms(hotelId, nsRooms);
List<SmHotelRoomEntity> localRooms = roomMapper.selectList(Wrappers.<SmHotelRoomEntity>lambdaQuery()
.eq(SmHotelRoomEntity::getHotelId, hotelId));
Map<String, String> roomNameByCode = new HashMap<>();
List<String> unmatchedRooms = new ArrayList<>();
for (SmHotelRoomEntity room : localRooms) {
if (room == null || StrUtil.isBlank(room.getRoomTypeCode())) {
continue;
}
roomNameByCode.put(room.getRoomTypeCode(), StrUtil.blankToDefault(room.getRoomTypeName(), ""));
if (!roomTypeToNs.containsKey(room.getRoomTypeCode())) {
unmatchedRooms.add(room.getRoomTypeCode()
+ (StrUtil.isNotBlank(room.getRoomTypeName()) ? "/" + room.getRoomTypeName() : ""));
}
}
List<SmHotelProductEntity> products = productMapper.selectList(Wrappers.<SmHotelProductEntity>lambdaQuery()
.eq(SmHotelProductEntity::getHotelId, hotelId)
.isNotNull(SmHotelProductEntity::getProductCode));
Map<String, Long> productToPlan = new LinkedHashMap<>();
int planOk = 0;
int productTotal = 0;
List<String> planErrors = new ArrayList<>();
for (SmHotelProductEntity p : products) {
if (p == null || StrUtil.isBlank(p.getProductCode()) || StrUtil.isBlank(p.getRoomTypeCode())) {
continue;
}
productTotal++;
String productLabel = p.getProductCode()
+ (StrUtil.isNotBlank(p.getProductName()) ? "/" + p.getProductName() : "");
String roomLabel = p.getRoomTypeCode()
+ (StrUtil.isNotBlank(roomNameByCode.get(p.getRoomTypeCode()))
? "/" + roomNameByCode.get(p.getRoomTypeCode()) : "");
Long nsRoomId = roomTypeToNs.get(p.getRoomTypeCode());
if (nsRoomId == null) {
planErrors.add(p.getProductCode() + ":无匹配圣诺房型");
planErrors.add(productLabel + "(房型 " + roomLabel + "):无匹配圣诺房型,计划/价格均未推");
continue;
}
int breakfast = p.getMealCount() == null ? 0 : Math.max(0, p.getMealCount());
@@ -688,14 +710,14 @@ public class SmHotelMidServiceImpl implements SmHotelMidService {
String.valueOf(nsHotelId), body);
long planId = extractShengnuoPlanId(planResp, breakfast);
if (planId <= 0) {
planErrors.add(p.getProductCode() + ":未返回 plan_id");
planErrors.add(productLabel + "(房型 " + roomLabel + "):未返回 plan_id,价格未推");
continue;
}
productToPlan.put(p.getProductCode(), planId);
planOk++;
}
catch (Exception ex) {
planErrors.add(p.getProductCode() + ":" + ex.getMessage());
planErrors.add(productLabel + "(房型 " + roomLabel + "):" + ex.getMessage());
log.warn("|下游计划推送[圣诺]|失败|hotelId={}|product={}|{}", hotelId, p.getProductCode(),
ex.getMessage());
}
@@ -713,22 +735,78 @@ public class SmHotelMidServiceImpl implements SmHotelMidService {
int priceChunks = pushShengnuoPriceChunks(partnerCode, ds, hotelId, nsHotelId, products, roomTypeToNs,
productToPlan, dailies, dailyFrom, dailyTo);
Map<String, Object> result = new HashMap<>(12);
Map<String, Object> result = new HashMap<>(16);
result.put("protocol", SmHotelDownstreamProtocol.SHENGNUO);
result.put("nsHotelId", nsHotelId);
result.put("eid", eid);
result.put("matchedRooms", roomTypeToNs.size());
result.put("unmatchedRooms", unmatchedRooms);
result.put("productTotal", productTotal);
result.put("planOk", planOk);
result.put("planErrors", planErrors);
result.put("priceChunks", priceChunks);
result.put("dailyRowCount", dailies.size());
result.put("windowFrom", dailyFrom.toString());
result.put("windowTo", dailyTo.toString());
log.info("|下游可售推送[圣诺]|hotelId={}|partner={}|nsHotelId={}|plans={}|chunks={}|", hotelId, partnerCode,
nsHotelId, planOk, priceChunks);
// 房型对不上 / 计划失败等不会打 HTTP 失败,单独落流水方便排查「哪些没推上」
recordShengnuoPushGaps(partnerCode, hotel, eid, nsHotelId, unmatchedRooms, productTotal, planOk, planErrors,
priceChunks, dailyFrom, dailyTo);
log.info("|下游可售推送[圣诺]|hotelId={}|partner={}|nsHotelId={}|plans={}/{}|chunks={}|gaps={}|", hotelId,
partnerCode, nsHotelId, planOk, productTotal, priceChunks, planErrors.size());
return result;
}
/**
* 圣诺推送缺口留底:房型未匹配、计划失败等会导致对应价格推不上,但整体 HTTP 可能仍成功。
* success=2 部分缺口;全部产品都没推上则 success=0。
*/
private void recordShengnuoPushGaps(String partnerCode, SmHotelEntity hotel, String eid, long nsHotelId,
List<String> unmatchedRooms, int productTotal, int planOk, List<String> planErrors, int priceChunks,
LocalDate dailyFrom, LocalDate dailyTo) {
boolean hasRoomGap = CollUtil.isNotEmpty(unmatchedRooms);
boolean hasPlanGap = CollUtil.isNotEmpty(planErrors);
boolean nothingPushed = productTotal > 0 && planOk <= 0;
if (!hasRoomGap && !hasPlanGap && !nothingPushed) {
return;
}
int gapCount = planErrors == null ? 0 : planErrors.size();
String summary;
if (nothingPushed) {
summary = "圣诺可售推送:全部 " + productTotal + " 个产品未推上(计划成功0),请核对房型匹配/plan";
}
else {
summary = "圣诺可售推送部分未推上:计划成功 " + planOk + "/" + productTotal + ",缺口 " + gapCount
+ (hasRoomGap ? ",未匹配房型 " + unmatchedRooms.size() + " 个" : "");
}
Map<String, Object> detail = new LinkedHashMap<>(12);
detail.put("eid", eid);
detail.put("nsHotelId", nsHotelId);
detail.put("windowFrom", dailyFrom == null ? null : dailyFrom.toString());
detail.put("windowTo", dailyTo == null ? null : dailyTo.toString());
detail.put("productTotal", productTotal);
detail.put("planOk", planOk);
detail.put("priceChunks", priceChunks);
detail.put("unmatchedRooms", unmatchedRooms == null ? List.of() : unmatchedRooms);
detail.put("planErrors", planErrors == null ? List.of() : planErrors);
String extHotelId = hotel == null ? eid
: StrUtil.blankToDefault(hotel.getSHotelId(),
StrUtil.blankToDefault(hotel.getHotelCode(),
hotel.getId() == null ? eid : String.valueOf(hotel.getId())));
SmHotelDownstreamPushLogEntity row = new SmHotelDownstreamPushLogEntity();
row.setPartnerCode(partnerCode);
row.setSyncLogId(DownstreamPushLogContext.syncLogId());
row.setHotelId(hotel == null ? null : hotel.getId());
row.setExtHotelId(extHotelId);
row.setAction("push_partial");
row.setSuccess(nothingPushed ? "0" : "2");
row.setErrorMsg(StrUtil.maxLength(summary, 900));
row.setResponseBody(StrUtil.maxLength(JSONUtil.toJsonStr(detail), 4000));
row.setCostMs(0L);
downstreamPushLogService.record(row);
log.warn("|下游推送缺口[圣诺]|hotelId={}|partner={}|{}|", hotel == null ? null : hotel.getId(), partnerCode,
summary);
}
/** 圣诺关房:pushHotelClose(整店) */
private Map<String, Object> closeHotelDailyToShengnuo(SmHotelEntity hotel, String partnerCode) {
downstreamClient.assertPartnerReady(partnerCode);