酒店
This commit is contained in:
1 parent
e5156e29e8
commit
708352cfcb
9 files changed
+164
-1
No files matched your search
+3
@@ -57,6 +57,9 @@ public class SmHotelHuanqiOrderController {
|
||||
header(request, "sign"), rawBody);
|
||||
partner = huanqiOrderService.resolvePartnerByAppKey(appKey);
|
||||
JSONObject payload = JSONUtil.parseObj(rawBody);
|
||||
if (StrUtil.isNotBlank(partner)) {
|
||||
payload.set("partnerCode", partner);
|
||||
}
|
||||
R<Map<String, Object>> r = hotelOrderService.checkBookableForDownstream(payload);
|
||||
if (r != null && r.getCode() == 0) {
|
||||
resp.put("code", 200);
|
||||
|
||||
+9
@@ -1,5 +1,7 @@
|
||||
package com.bsd.pjcloud.supermarket.service;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 下游资源自动推送:本地/回调变更后防抖推;日更按授权范围扫。
|
||||
*/
|
||||
@@ -10,6 +12,13 @@ public interface SmHotelDownstreamAutoPushService {
|
||||
*/
|
||||
void scheduleAfterHotelChanged(Long hotelId);
|
||||
|
||||
/**
|
||||
* 下游可订校验失败后异步补偿:上游酒店先按入住区间重拉价态,再向该伙伴推送最新资源;
|
||||
* 本地酒店跳过上游拉取,直接向该伙伴推送。partnerCode 为空时仅重拉(上游)并走通用自动推。
|
||||
*/
|
||||
void scheduleRefreshAndPushAfterCheckFail(Long hotelId, String partnerCode, LocalDate checkIn,
|
||||
LocalDate checkOut);
|
||||
|
||||
/**
|
||||
* 删产品前:对指定产品先向下游关房(避免删光后无法组关房报文)。
|
||||
*/
|
||||
|
||||
+104
@@ -7,6 +7,7 @@ import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.common.data.tenant.TenantBroker;
|
||||
import com.bsd.pjcloud.common.data.tenant.TenantContextHolder;
|
||||
import com.bsd.pjcloud.supermarket.config.HotelDownstreamProperties;
|
||||
import com.bsd.pjcloud.supermarket.constant.SmHotelDataSource;
|
||||
import com.bsd.pjcloud.supermarket.constant.SmHotelDownstreamProtocol;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelDownstreamPushLogEntity;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelEntity;
|
||||
@@ -24,6 +25,7 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.support.TransactionSynchronization;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -70,6 +72,9 @@ public class SmHotelDownstreamAutoPushServiceImpl implements SmHotelDownstreamAu
|
||||
|
||||
private final ConcurrentHashMap<Long, ScheduledFuture<?>> pendingByHotel = new ConcurrentHashMap<>();
|
||||
|
||||
/** 验价失败补偿:hotelId|partner 防抖,避免下游短时间连验刷爆上游/推送 */
|
||||
private final ConcurrentHashMap<String, ScheduledFuture<?>> pendingCheckFail = new ConcurrentHashMap<>();
|
||||
|
||||
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(r -> {
|
||||
Thread t = new Thread(r, "sm-hotel-downstream-auto-push");
|
||||
t.setDaemon(true);
|
||||
@@ -98,6 +103,93 @@ public class SmHotelDownstreamAutoPushServiceImpl implements SmHotelDownstreamAu
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduleRefreshAndPushAfterCheckFail(Long hotelId, String partnerCode, LocalDate checkIn,
|
||||
LocalDate checkOut) {
|
||||
if (hotelId == null) {
|
||||
return;
|
||||
}
|
||||
String partner = StrUtil.trim(partnerCode);
|
||||
String key = hotelId + "|" + StrUtil.blankToDefault(partner, "*");
|
||||
Long tenantId = TenantContextHolder.getTenantId();
|
||||
if (tenantId == null) {
|
||||
tenantId = 1L;
|
||||
}
|
||||
Long tid = tenantId;
|
||||
LocalDate in = checkIn == null ? LocalDate.now() : checkIn;
|
||||
LocalDate out = checkOut == null ? in.plusDays(1) : checkOut;
|
||||
pendingCheckFail.compute(key, (k, old) -> {
|
||||
if (old != null) {
|
||||
old.cancel(false);
|
||||
}
|
||||
// 短防抖:先回验价响应,再异步拉/推,合并下游连验
|
||||
return scheduler.schedule(() -> {
|
||||
pendingCheckFail.remove(k);
|
||||
try {
|
||||
TenantBroker.runAs(tid, t -> doRefreshAndPushAfterCheckFail(hotelId, partner, in, out));
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.warn("|{}|验价失败补偿异常|hotelId={}|partner={}|err={}|", TAG, hotelId, partner,
|
||||
e.getMessage());
|
||||
}
|
||||
}, 2, TimeUnit.SECONDS);
|
||||
});
|
||||
log.info("|{}|已安排验价失败补偿|hotelId={}|partner={}|{}~{}|", TAG, hotelId, partner, in, out);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上游:按入住区间重拉价态(至少覆盖入住日,不足 7 天补到 7 天);本地:跳过拉取。
|
||||
* 随后向指定伙伴直推;无伙伴则走通用自动推。
|
||||
*/
|
||||
private void doRefreshAndPushAfterCheckFail(Long hotelId, String partner, LocalDate checkIn, LocalDate checkOut) {
|
||||
SmHotelEntity hotel = hotelMapper.selectById(hotelId);
|
||||
if (hotel == null) {
|
||||
return;
|
||||
}
|
||||
boolean local = SmHotelDataSource.isLocal(hotel.getSheetName());
|
||||
if (!local) {
|
||||
LocalDate start = checkIn;
|
||||
LocalDate endExclusive = checkOut != null && checkOut.isAfter(checkIn) ? checkOut : checkIn.plusDays(1);
|
||||
// 验价失败常因价态过期:至少拉 7 天,最多 30 天,避免一次过重
|
||||
if (endExclusive.isBefore(start.plusDays(7))) {
|
||||
endExclusive = start.plusDays(7);
|
||||
}
|
||||
if (endExclusive.isAfter(start.plusDays(30))) {
|
||||
endExclusive = start.plusDays(30);
|
||||
}
|
||||
try {
|
||||
if (SmHotelDataSource.isWritableByChannel(hotel.getSheetName(), SmHotelDataSource.SHANGYOU)
|
||||
|| StrUtil.isBlank(SmHotelDataSource.normalize(hotel.getSheetName()))) {
|
||||
R<Map<String, Object>> pulled = midService.pullPriceRange(hotelId, start, endExclusive);
|
||||
log.info("|{}|验价失败已重拉上游|hotelId={}|partner={}|{}~{}|ok={}|msg={}|", TAG, hotelId, partner,
|
||||
start, endExclusive, pulled != null && pulled.isOk(),
|
||||
pulled == null ? null : pulled.getMsg());
|
||||
}
|
||||
else {
|
||||
log.info("|{}|验价失败跳过重拉(非商游可写来源)|hotelId={}|ds={}|", TAG, hotelId,
|
||||
hotel.getSheetName());
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.warn("|{}|验价失败重拉上游失败,仍尝试推本地快照|hotelId={}|err={}|", TAG, hotelId, e.getMessage());
|
||||
}
|
||||
}
|
||||
if (StrUtil.isNotBlank(partner)) {
|
||||
long t0 = System.currentTimeMillis();
|
||||
try {
|
||||
R<Map<String, Object>> r = midService.pushToDownstream(hotelId, partner);
|
||||
log.info("|{}|验价失败已直推伙伴|hotelId={}|partner={}|ok={}|costMs={}|msg={}|", TAG, hotelId, partner,
|
||||
r != null && r.isOk(), System.currentTimeMillis() - t0, r == null ? null : r.getMsg());
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.warn("|{}|验价失败直推伙伴失败|hotelId={}|partner={}|err={}|", TAG, hotelId, partner, e.getMessage());
|
||||
}
|
||||
return;
|
||||
}
|
||||
// 无指定伙伴:走原有命中范围自动推
|
||||
scheduleAfterHotelChanged(hotelId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeProductsOnMatchedPartners(Long hotelId, String productCode, String roomTypeCode) {
|
||||
if (hotelId == null || StrUtil.isBlank(productCode)) {
|
||||
@@ -347,6 +439,18 @@ public class SmHotelDownstreamAutoPushServiceImpl implements SmHotelDownstreamAu
|
||||
|
||||
@PreDestroy
|
||||
public void shutdown() {
|
||||
pendingCheckFail.values().forEach(f -> {
|
||||
if (f != null) {
|
||||
f.cancel(false);
|
||||
}
|
||||
});
|
||||
pendingCheckFail.clear();
|
||||
pendingByHotel.values().forEach(f -> {
|
||||
if (f != null) {
|
||||
f.cancel(false);
|
||||
}
|
||||
});
|
||||
pendingByHotel.clear();
|
||||
scheduler.shutdownNow();
|
||||
}
|
||||
}
|
||||
+3
@@ -136,6 +136,9 @@ public class SmHotelDownstreamInboundRetryServiceImpl implements SmHotelDownstre
|
||||
JSONObject payload = JSONUtil.parseObj(rawBody);
|
||||
Map<String, Object> resp = new LinkedHashMap<>(4);
|
||||
if ("inbound_check".equals(action)) {
|
||||
if (StrUtil.isNotBlank(partner)) {
|
||||
payload.set("partnerCode", partner);
|
||||
}
|
||||
R<Map<String, Object>> r = hotelOrderService.checkBookableForDownstream(payload);
|
||||
if (r != null && r.getCode() == 0) {
|
||||
resp.put("code", 200);
|
||||
|
||||
+3
@@ -184,6 +184,9 @@ public class SmHotelHuanqiOrderServiceImpl implements SmHotelHuanqiOrderService
|
||||
checkBody.put("checkIn", order.getCheckInDate() == null ? null : order.getCheckInDate().toString());
|
||||
checkBody.put("checkOut", order.getCheckOutDate() == null ? null : order.getCheckOutDate().toString());
|
||||
checkBody.put("roomCnt", order.getCount() == null ? 1 : order.getCount());
|
||||
if (StrUtil.isNotBlank(partnerCode)) {
|
||||
checkBody.put("partnerCode", partnerCode);
|
||||
}
|
||||
// 推单履约预检不带 price:环启 price 是结算价,与本地日历卖价口径不同,避免误拦
|
||||
R<Map<String, Object>> check = hotelOrderService.checkBookableForDownstream(checkBody);
|
||||
boolean bookable = check != null && check.getCode() == 0 && check.getData() != null
|
||||
|
||||
+6
@@ -83,6 +83,9 @@ public class SmHotelHuidingOrderServiceImpl implements SmHotelHuidingOrderServic
|
||||
public Map<String, Object> check(String partnerCode, JSONObject data) {
|
||||
Map<String, Object> outer = ok200();
|
||||
Map<String, Object> checkBody = toCheckBody(data);
|
||||
if (StrUtil.isNotBlank(partnerCode)) {
|
||||
checkBody.put("partnerCode", partnerCode);
|
||||
}
|
||||
R<Map<String, Object>> r = hotelOrderService.checkBookableForDownstream(checkBody);
|
||||
Map<String, Object> inner = new LinkedHashMap<>(8);
|
||||
List<Map<String, Object>> priceList = new ArrayList<>();
|
||||
@@ -175,6 +178,9 @@ public class SmHotelHuidingOrderServiceImpl implements SmHotelHuidingOrderServic
|
||||
checkBody.put("checkOut", mapped.checkOut.toString());
|
||||
checkBody.put("roomCnt", mapped.quantity);
|
||||
checkBody.put("price", mapped.orderAmount);
|
||||
if (StrUtil.isNotBlank(partnerCode)) {
|
||||
checkBody.put("partnerCode", partnerCode);
|
||||
}
|
||||
R<Map<String, Object>> check = hotelOrderService.checkBookableForDownstream(checkBody);
|
||||
boolean bookable = check != null && check.getCode() == 0 && check.getData() != null
|
||||
&& Boolean.TRUE.equals(check.getData().get("bookable"));
|
||||
|
||||
+4
@@ -359,6 +359,10 @@ public class SmHotelOpenApiServiceImpl implements SmHotelOpenApiService {
|
||||
checkBody.put("checkOut", body.get("checkOutDate"));
|
||||
checkBody.put("roomCnt", body.get("count") != null ? body.get("count") : body.get("quantity"));
|
||||
checkBody.put("price", body.get("totalAmount") != null ? body.get("totalAmount") : body.get("totalPrice"));
|
||||
String partner = resolvePartnerCode(app);
|
||||
if (StrUtil.isNotBlank(partner)) {
|
||||
checkBody.put("partnerCode", partner);
|
||||
}
|
||||
R<Map<String, Object>> r = hotelOrderService.checkBookableForDownstream(checkBody);
|
||||
if (r == null || !r.isOk()) {
|
||||
return r == null ? R.failed("可订校验失败") : r;
|
||||
|
||||
+29
-1
@@ -1075,6 +1075,7 @@ public class SmHotelOrderServiceImpl extends ServiceImpl<SmHotelOrderMapper, SmH
|
||||
}
|
||||
}
|
||||
BigDecimal expectPrice = toDecimal(body.get("price") != null ? body.get("price") : body.get("totalAmount"));
|
||||
String partnerCode = StrUtil.trim(str(body.get("partnerCode")));
|
||||
|
||||
data.put("extHotelId", extHotelId);
|
||||
data.put("extProductId", extProductId);
|
||||
@@ -1082,6 +1083,9 @@ public class SmHotelOrderServiceImpl extends ServiceImpl<SmHotelOrderMapper, SmH
|
||||
data.put("checkIn", checkIn == null ? null : checkIn.toString());
|
||||
data.put("checkOut", checkOut == null ? null : checkOut.toString());
|
||||
data.put("roomCnt", roomCnt);
|
||||
if (StrUtil.isNotBlank(partnerCode)) {
|
||||
data.put("partnerCode", partnerCode);
|
||||
}
|
||||
|
||||
if (StrUtil.isBlank(extHotelId)) {
|
||||
reasons.add("extHotelId 不能为空");
|
||||
@@ -1126,6 +1130,8 @@ public class SmHotelOrderServiceImpl extends ServiceImpl<SmHotelOrderMapper, SmH
|
||||
data.put("productExists", false);
|
||||
data.put("bookable", false);
|
||||
data.put("reasons", reasons);
|
||||
// 产品缺失也触发补偿:上游酒店重拉后推下游,便于补齐价态/产品
|
||||
scheduleRefreshAfterDownstreamCheckFail(hotel.getId(), partnerCode, checkIn, checkOut);
|
||||
return R.ok(data);
|
||||
}
|
||||
data.put("productExists", true);
|
||||
@@ -1194,15 +1200,37 @@ public class SmHotelOrderServiceImpl extends ServiceImpl<SmHotelOrderMapper, SmH
|
||||
}
|
||||
data.put("upstreamAvailChecked", upstreamChecked);
|
||||
data.put("needUpstreamDoubleCheck", !localDs && !upstreamChecked);
|
||||
data.put("bookable", reasons.isEmpty());
|
||||
boolean bookable = reasons.isEmpty();
|
||||
data.put("bookable", bookable);
|
||||
data.put("reasons", reasons);
|
||||
// 仅价差:圣诺等可落单待人工确认,其它硬伤仍拒单(上游不可订不算价差)
|
||||
boolean priceMismatchOnly = !reasons.isEmpty() && reasons.stream()
|
||||
.allMatch(r -> r != null && String.valueOf(r).contains("结算价不一致"));
|
||||
data.put("priceMismatchOnly", priceMismatchOnly);
|
||||
if (!bookable) {
|
||||
// 验价失败异步补偿:上游先重拉价态,再向该伙伴推最新资源,避免下游一直用旧快照
|
||||
scheduleRefreshAfterDownstreamCheckFail(hotel.getId(), partnerCode, checkIn, checkOut);
|
||||
}
|
||||
return R.ok(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下游可订校验失败后异步补偿(不阻塞验价响应)。
|
||||
*/
|
||||
private void scheduleRefreshAfterDownstreamCheckFail(Long hotelId, String partnerCode, LocalDate checkIn,
|
||||
LocalDate checkOut) {
|
||||
if (hotelId == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
downstreamAutoPushService.scheduleRefreshAndPushAfterCheckFail(hotelId, partnerCode, checkIn, checkOut);
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.warn("|下游预检|安排验价失败补偿失败|hotelId={}|partner={}|err={}|", hotelId, partnerCode,
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 下游预检专用:调上游 checkAvailability。
|
||||
* @return null=可订;非空=不可订/异常原因(写入 reasons)
|
||||
|
||||
+3
@@ -127,6 +127,9 @@ public class SmHotelShengnuoOrderServiceImpl implements SmHotelShengnuoOrderServ
|
||||
checkBody.put("checkOut", m.checkOut.toString());
|
||||
checkBody.put("roomCnt", m.quantity);
|
||||
checkBody.put("price", m.orderAmount);
|
||||
if (StrUtil.isNotBlank(partnerCode)) {
|
||||
checkBody.put("partnerCode", partnerCode);
|
||||
}
|
||||
R<Map<String, Object>> check = hotelOrderService.checkBookableForDownstream(checkBody);
|
||||
Map<String, Object> checkData = check != null && check.getCode() == 0 ? check.getData() : null;
|
||||
boolean bookable = checkData != null && Boolean.TRUE.equals(checkData.get("bookable"));
|
||||
|
||||
Reference in new issue
Block a user