酒店
This commit is contained in:
1 parent
b05616400b
commit
0283fe84c3
10 files changed
+653
-21
No files matched your search
+1
-1
@@ -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/push_partial")
|
||||
@Schema(description = "动作:hotel_sync/apartment_sync/product_sync/daily_sync/push_skip/push_partial/auto_push")
|
||||
private String action;
|
||||
|
||||
@Schema(description = "完整请求URL")
|
||||
|
||||
+11
-4
@@ -5,6 +5,7 @@ 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.SmHotelDownstreamInboundRetryService;
|
||||
import com.bsd.pjcloud.supermarket.service.SmHotelDownstreamPushLogService;
|
||||
import com.bsd.pjcloud.supermarket.service.SmHotelMidService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -29,6 +30,8 @@ public class SmHotelDownstreamPushLogController {
|
||||
|
||||
private final SmHotelMidService midService;
|
||||
|
||||
private final SmHotelDownstreamInboundRetryService inboundRetryService;
|
||||
|
||||
@Operation(summary = "分页")
|
||||
@GetMapping("/page")
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_smHotelDownstreamPushLog_view')")
|
||||
@@ -45,7 +48,7 @@ public class SmHotelDownstreamPushLogController {
|
||||
}
|
||||
|
||||
@SysLog("下游推送流水重试")
|
||||
@Operation(summary = "按流水关联酒店重新推送到下游(整店:酒店/房型/产品/日历)")
|
||||
@Operation(summary = "重试:资源流水整店重推;入站流水按原请求体重放业务(不验签)")
|
||||
@PostMapping("/retry/{id}")
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_smHotelDownstreamPushLog_retry')")
|
||||
public R<Map<String, Object>> retry(@PathVariable Long id) {
|
||||
@@ -53,15 +56,19 @@ public class SmHotelDownstreamPushLogController {
|
||||
if (row == null) {
|
||||
return R.failed("流水不存在");
|
||||
}
|
||||
String action = StrUtil.blankToDefault(row.getAction(), "");
|
||||
if (action.startsWith("inbound_")) {
|
||||
return inboundRetryService.retryInbound(row);
|
||||
}
|
||||
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("订单查询类流水不支持从此处重推,请使用订单对接能力");
|
||||
if ("order_query".equalsIgnoreCase(action) || "order_detail".equalsIgnoreCase(action)
|
||||
|| "operation_order".equalsIgnoreCase(action) || "auto_push".equalsIgnoreCase(action)) {
|
||||
return R.failed("该类流水不支持从此处重推");
|
||||
}
|
||||
return midService.pushToDownstream(row.getHotelId(), row.getPartnerCode().trim());
|
||||
}
|
||||
|
||||
+8
-1
@@ -62,7 +62,10 @@ public class DownstreamInboundAccessLog {
|
||||
if (o == null) {
|
||||
return null;
|
||||
}
|
||||
String direct = StrUtil.blankToDefault(o.getStr("hotelId"), o.getStr("extHotelId"));
|
||||
// 圣诺 hotel_id;慧订/环启 hotelId、extHotelId
|
||||
String direct = StrUtil.blankToDefault(o.getStr("hotelId"),
|
||||
StrUtil.blankToDefault(o.getStr("extHotelId"),
|
||||
StrUtil.blankToDefault(str(o.get("hotel_id")), o.getStr("hotelCode"))));
|
||||
if (StrUtil.isNotBlank(direct)) {
|
||||
return direct;
|
||||
}
|
||||
@@ -133,4 +136,8 @@ public class DownstreamInboundAccessLog {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String str(Object o) {
|
||||
return o == null ? null : String.valueOf(o);
|
||||
}
|
||||
}
|
||||
+45
@@ -14,6 +14,8 @@ import java.util.concurrent.TimeUnit;
|
||||
/**
|
||||
* 圣诺侧酒店/房型/计划 ID 与本地映射(推送 getInfo/plan 后写入,订单入站反查)。
|
||||
* 不改表结构,落 Redis;TTL 默认 30 天。
|
||||
* <p>
|
||||
* 同时写伙伴维度反查 + 按 nsHotelId 全局反查,避免入站伙伴码与推送时不一致时找不到。
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@@ -23,6 +25,9 @@ public class ShengnuoIdMappingCache {
|
||||
|
||||
private static final String REV = "sm:hotel:shengnuo:rev:";
|
||||
|
||||
/** nsHotelId → partner|localHotelId,跨伙伴兜底 */
|
||||
private static final String REV_NS = "sm:hotel:shengnuo:ns:";
|
||||
|
||||
private static final long TTL_DAYS = 30L;
|
||||
|
||||
private final StringRedisTemplate redisTemplate;
|
||||
@@ -49,6 +54,7 @@ public class ShengnuoIdMappingCache {
|
||||
JSONObject o = new JSONObject();
|
||||
o.set("localHotelId", localHotelId);
|
||||
o.set("nsHotelId", nsHotelId);
|
||||
o.set("partner", partner);
|
||||
o.set("rooms", rooms);
|
||||
o.set("plans", plans);
|
||||
o.set("revRooms", revRooms);
|
||||
@@ -56,6 +62,32 @@ public class ShengnuoIdMappingCache {
|
||||
redisTemplate.opsForValue().set(KEY + partner + ":" + localHotelId, o.toString(), TTL_DAYS, TimeUnit.DAYS);
|
||||
redisTemplate.opsForValue().set(REV + partner + ":" + nsHotelId, String.valueOf(localHotelId), TTL_DAYS,
|
||||
TimeUnit.DAYS);
|
||||
redisTemplate.opsForValue().set(REV_NS + nsHotelId, partner + "|" + localHotelId, TTL_DAYS, TimeUnit.DAYS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按圣诺酒店 id 反查本地酒店。优先当前伙伴 key,再回落全局 ns 索引。
|
||||
* @return [0]=用于读 map 的 partner,[1]=localHotelId;找不到返回 null
|
||||
*/
|
||||
public String[] resolveLocalByNs(String partner, long nsHotelId) {
|
||||
if (nsHotelId <= 0) {
|
||||
return null;
|
||||
}
|
||||
if (StrUtil.isNotBlank(partner)) {
|
||||
Long local = localHotelIdByNsStrict(partner, nsHotelId);
|
||||
if (local != null) {
|
||||
return new String[] { partner, String.valueOf(local) };
|
||||
}
|
||||
}
|
||||
String raw = redisTemplate.opsForValue().get(REV_NS + nsHotelId);
|
||||
if (StrUtil.isBlank(raw)) {
|
||||
return null;
|
||||
}
|
||||
String[] parts = raw.split("\\|", 2);
|
||||
if (parts.length != 2 || StrUtil.hasBlank(parts[0], parts[1])) {
|
||||
return null;
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
public JSONObject getByLocal(String partner, Long localHotelId) {
|
||||
@@ -75,6 +107,19 @@ public class ShengnuoIdMappingCache {
|
||||
}
|
||||
|
||||
public Long localHotelIdByNs(String partner, long nsHotelId) {
|
||||
String[] hit = resolveLocalByNs(partner, nsHotelId);
|
||||
if (hit == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Long.parseLong(hit[1].trim());
|
||||
}
|
||||
catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Long localHotelIdByNsStrict(String partner, long nsHotelId) {
|
||||
if (StrUtil.isBlank(partner) || nsHotelId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
+5
@@ -19,4 +19,9 @@ public interface SmHotelDownstreamAutoPushService {
|
||||
* 日更:对 resource-push-join-schedule=true 的启用伙伴,启动范围全量推(可售/关房/跳过由 push 内部分类)。
|
||||
*/
|
||||
void runScheduledScopePush();
|
||||
|
||||
/**
|
||||
* 保存日历/产品等后的前端提示文案;自动推关闭时说明需手动推。
|
||||
*/
|
||||
String autoPushScheduleHint();
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package com.bsd.pjcloud.supermarket.service;
|
||||
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelDownstreamPushLogEntity;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 下游入站流水重放:用流水里的请求体重跑业务(跳过对方签名/nonce),便于联调自助重试。
|
||||
*/
|
||||
public interface SmHotelDownstreamInboundRetryService {
|
||||
|
||||
R<Map<String, Object>> retryInbound(SmHotelDownstreamPushLogEntity row);
|
||||
}
|
||||
+53
-2
@@ -2,15 +2,18 @@ package com.bsd.pjcloud.supermarket.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
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.entity.SmHotelDownstreamPushLogEntity;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelEntity;
|
||||
import com.bsd.pjcloud.supermarket.mapper.SmHotelMapper;
|
||||
import com.bsd.pjcloud.supermarket.service.SmHotelChannelConfigService;
|
||||
import com.bsd.pjcloud.supermarket.service.SmHotelChannelDeptService;
|
||||
import com.bsd.pjcloud.supermarket.service.SmHotelDownstreamAutoPushService;
|
||||
import com.bsd.pjcloud.supermarket.service.SmHotelDownstreamPushLogService;
|
||||
import com.bsd.pjcloud.supermarket.service.SmHotelMidService;
|
||||
import com.bsd.pjcloud.supermarket.service.SmHotelSyncService;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
@@ -49,15 +52,19 @@ public class SmHotelDownstreamAutoPushServiceImpl implements SmHotelDownstreamAu
|
||||
|
||||
private final SmHotelSyncService syncService;
|
||||
|
||||
private final SmHotelDownstreamPushLogService downstreamPushLogService;
|
||||
|
||||
public SmHotelDownstreamAutoPushServiceImpl(HotelDownstreamProperties yamlDefaults,
|
||||
SmHotelChannelConfigService channelConfigService, SmHotelChannelDeptService channelDeptService,
|
||||
SmHotelMapper hotelMapper, @Lazy SmHotelMidService midService, @Lazy SmHotelSyncService syncService) {
|
||||
SmHotelMapper hotelMapper, @Lazy SmHotelMidService midService, @Lazy SmHotelSyncService syncService,
|
||||
SmHotelDownstreamPushLogService downstreamPushLogService) {
|
||||
this.yamlDefaults = yamlDefaults;
|
||||
this.channelConfigService = channelConfigService;
|
||||
this.channelDeptService = channelDeptService;
|
||||
this.hotelMapper = hotelMapper;
|
||||
this.midService = midService;
|
||||
this.syncService = syncService;
|
||||
this.downstreamPushLogService = downstreamPushLogService;
|
||||
}
|
||||
|
||||
private final ConcurrentHashMap<Long, ScheduledFuture<?>> pendingByHotel = new ConcurrentHashMap<>();
|
||||
@@ -147,6 +154,14 @@ public class SmHotelDownstreamAutoPushServiceImpl implements SmHotelDownstreamAu
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String autoPushScheduleHint() {
|
||||
if (!isAutoPushEnabled()) {
|
||||
return "自动推下游已关闭,请到酒店列表手动「推下游」";
|
||||
}
|
||||
return "约" + resolveDebounceSeconds() + "秒后自动推已授权下游(圣诺/环启等),无需再点手动推";
|
||||
}
|
||||
|
||||
private void scheduleDebounced(Long hotelId) {
|
||||
Long tenantId = TenantContextHolder.getTenantId();
|
||||
if (tenantId == null) {
|
||||
@@ -178,12 +193,16 @@ public class SmHotelDownstreamAutoPushServiceImpl implements SmHotelDownstreamAu
|
||||
}
|
||||
List<String> partners = resolvePartners(hotel);
|
||||
if (CollUtil.isEmpty(partners)) {
|
||||
log.debug("|{}|无命中下游范围|hotelId={}|", TAG, hotelId);
|
||||
// 提到 info:改价后「没推出去」最常见原因是渠道范围未命中
|
||||
log.info("|{}|无命中下游范围,跳过自动推|hotelId={}|deptId={}|", TAG, hotelId, hotel.getDeptId());
|
||||
return;
|
||||
}
|
||||
for (String partner : partners) {
|
||||
long t0 = System.currentTimeMillis();
|
||||
try {
|
||||
R<Map<String, Object>> r = midService.pushToDownstream(hotelId, partner);
|
||||
long cost = System.currentTimeMillis() - t0;
|
||||
recordAutoPushSummary(hotel, partner, r, null, cost);
|
||||
if (r != null && r.getCode() == 0) {
|
||||
log.info("|{}|完成|hotelId={}|partner={}|data={}|", TAG, hotelId, partner, r.getData());
|
||||
}
|
||||
@@ -193,11 +212,43 @@ public class SmHotelDownstreamAutoPushServiceImpl implements SmHotelDownstreamAu
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
long cost = System.currentTimeMillis() - t0;
|
||||
recordAutoPushSummary(hotel, partner, null, e.getMessage(), cost);
|
||||
log.warn("|{}|异常|hotelId={}|partner={}|err={}|", TAG, hotelId, partner, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 变更后自动推摘要流水(不受 fail_only 限制),方便在「下游推送日志」核对改价是否推出去。
|
||||
*/
|
||||
private void recordAutoPushSummary(SmHotelEntity hotel, String partner, R<Map<String, Object>> r, String exMsg,
|
||||
long costMs) {
|
||||
try {
|
||||
boolean ok = r != null && r.getCode() == 0;
|
||||
SmHotelDownstreamPushLogEntity row = new SmHotelDownstreamPushLogEntity();
|
||||
row.setPartnerCode(partner);
|
||||
row.setHotelId(hotel == null ? null : hotel.getId());
|
||||
row.setExtHotelId(hotel == null ? null
|
||||
: StrUtil.blankToDefault(hotel.getSHotelId(),
|
||||
StrUtil.blankToDefault(hotel.getHotelCode(),
|
||||
hotel.getId() == null ? null : String.valueOf(hotel.getId()))));
|
||||
row.setAction("auto_push");
|
||||
row.setSuccess(ok ? "1" : "0");
|
||||
String msg = ok ? "变更后自动推送完成"
|
||||
: StrUtil.blankToDefault(exMsg, r == null ? "自动推送失败" : StrUtil.blankToDefault(r.getMsg(), "自动推送失败"));
|
||||
row.setErrorMsg(StrUtil.maxLength(msg, 900));
|
||||
if (r != null && r.getData() != null) {
|
||||
row.setResponseBody(StrUtil.maxLength(JSONUtil.toJsonStr(r.getData()), 4000));
|
||||
}
|
||||
row.setCostMs(costMs);
|
||||
downstreamPushLogService.record(row);
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.warn("|{}|摘要流水落库失败|partner={}|err={}|", TAG, partner, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> resolvePartners(SmHotelEntity hotel) {
|
||||
List<String> enabled = channelConfigService.listEnabledDownstreamPartners();
|
||||
if (CollUtil.isEmpty(enabled)) {
|
||||
|
||||
+259
@@ -0,0 +1,259 @@
|
||||
package com.bsd.pjcloud.supermarket.service.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.supermarket.config.HotelDownstreamProperties;
|
||||
import com.bsd.pjcloud.supermarket.constant.SmHotelDownstreamProtocol;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelDownstreamPushLogEntity;
|
||||
import com.bsd.pjcloud.supermarket.integration.downstream.DownstreamInboundAccessLog;
|
||||
import com.bsd.pjcloud.supermarket.integration.downstream.shengnuo.ShengnuoDownstreamHotelAdapter;
|
||||
import com.bsd.pjcloud.supermarket.service.SmHotelChannelConfigService;
|
||||
import com.bsd.pjcloud.supermarket.service.SmHotelDownstreamInboundRetryService;
|
||||
import com.bsd.pjcloud.supermarket.service.SmHotelHuanqiOrderService;
|
||||
import com.bsd.pjcloud.supermarket.service.SmHotelHuidingOrderService;
|
||||
import com.bsd.pjcloud.supermarket.service.SmHotelOrderService;
|
||||
import com.bsd.pjcloud.supermarket.service.SmHotelShengnuoOrderService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SmHotelDownstreamInboundRetryServiceImpl implements SmHotelDownstreamInboundRetryService {
|
||||
|
||||
private static final String TAG = "入站重放";
|
||||
|
||||
private final SmHotelChannelConfigService channelConfigService;
|
||||
|
||||
private final SmHotelShengnuoOrderService shengnuoOrderService;
|
||||
|
||||
private final SmHotelHuidingOrderService huidingOrderService;
|
||||
|
||||
private final SmHotelHuanqiOrderService huanqiOrderService;
|
||||
|
||||
private final SmHotelOrderService hotelOrderService;
|
||||
|
||||
private final DownstreamInboundAccessLog inboundAccessLog;
|
||||
|
||||
@Override
|
||||
public R<Map<String, Object>> retryInbound(SmHotelDownstreamPushLogEntity row) {
|
||||
if (row == null) {
|
||||
return R.failed("流水不存在");
|
||||
}
|
||||
String action = StrUtil.blankToDefault(row.getAction(), "").trim();
|
||||
if (!action.startsWith("inbound_")) {
|
||||
return R.failed("非入站流水,请用资源重推");
|
||||
}
|
||||
if (StrUtil.isBlank(row.getRequestBody())) {
|
||||
return R.failed("流水无请求体,无法重放");
|
||||
}
|
||||
String partner = StrUtil.trim(row.getPartnerCode());
|
||||
String url = StrUtil.blankToDefault(row.getRequestUrl(), "");
|
||||
String protocol = resolveProtocol(partner, url, action);
|
||||
long t0 = System.currentTimeMillis();
|
||||
Map<String, Object> resp;
|
||||
try {
|
||||
if (SmHotelDownstreamProtocol.isShengnuo(protocol) || urlContains(url, "shengnuo")) {
|
||||
resp = replayShengnuo(partner, action, row.getRequestBody());
|
||||
if (StrUtil.isBlank(partner)) {
|
||||
partner = peekShengnuoPartner(row.getRequestBody());
|
||||
}
|
||||
}
|
||||
else if (SmHotelDownstreamProtocol.isHuiding(protocol) || urlContains(url, "huiding")) {
|
||||
resp = replayHuiding(partner, action, row.getRequestBody());
|
||||
}
|
||||
else if (SmHotelDownstreamProtocol.isHuanqi(protocol) || urlContains(url, "/third/party/api/order")) {
|
||||
resp = replayHuanqi(partner, action, row.getRequestBody());
|
||||
}
|
||||
else {
|
||||
return R.failed("无法识别入站协议(伙伴=" + partner + ",url=" + url + "),请确认渠道协议配置");
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.warn("|{}|失败|action={}|partner={}|{}|", TAG, action, partner, e.getMessage());
|
||||
resp = failResp(e.getMessage());
|
||||
}
|
||||
inboundAccessLog.record(partner, action, StrUtil.blankToDefault(url, "retry:" + action),
|
||||
row.getRequestBody(), resp, System.currentTimeMillis() - t0);
|
||||
Integer code = extractCode(resp);
|
||||
boolean ok = code != null && (code == 0 || code == 200);
|
||||
String msg = extractMsg(resp);
|
||||
Map<String, Object> data = new LinkedHashMap<>(4);
|
||||
data.put("inboundRetry", true);
|
||||
data.put("action", action);
|
||||
data.put("partnerCode", partner);
|
||||
data.put("result", resp);
|
||||
if (ok) {
|
||||
return R.ok(data, StrUtil.blankToDefault(msg, "入站重放成功"));
|
||||
}
|
||||
return R.failed(data, StrUtil.blankToDefault(msg, "入站重放失败"));
|
||||
}
|
||||
|
||||
private Map<String, Object> replayShengnuo(String partner, String action, String rawBody) {
|
||||
JSONObject body = JSONUtil.parseObj(rawBody);
|
||||
String p = StrUtil.blankToDefault(partner, peekShengnuoPartner(rawBody));
|
||||
if (StrUtil.isBlank(p)) {
|
||||
throw new IllegalArgumentException("缺少下游伙伴,无法重放圣诺入站");
|
||||
}
|
||||
return switch (action) {
|
||||
case "inbound_create" -> shengnuoOrderService.createOrder(p, body);
|
||||
case "inbound_cancel" -> shengnuoOrderService.orderCancel(p, body);
|
||||
case "inbound_timeout" -> shengnuoOrderService.orderConfirmTimeOut(p, body);
|
||||
default -> throw new IllegalArgumentException("圣诺不支持重放动作:" + action);
|
||||
};
|
||||
}
|
||||
|
||||
private Map<String, Object> replayHuiding(String partner, String action, String rawBody) {
|
||||
JSONObject envelope = JSONUtil.parseObj(rawBody);
|
||||
if ("inbound_notify".equals(action)) {
|
||||
String pass = StrUtil.trim(envelope.getStr("pass"));
|
||||
String ordercode = StrUtil.trim(envelope.getStr("ordercode"));
|
||||
String childcode = StrUtil.trim(envelope.getStr("childcode"));
|
||||
return huidingOrderService.notify(pass, ordercode, childcode);
|
||||
}
|
||||
if (StrUtil.isBlank(partner)) {
|
||||
throw new IllegalArgumentException("缺少下游伙伴,无法重放慧订入站");
|
||||
}
|
||||
JSONObject data = parseHuidingData(envelope);
|
||||
return switch (action) {
|
||||
case "inbound_check" -> huidingOrderService.check(partner, data);
|
||||
case "inbound_create" -> huidingOrderService.create(partner, data);
|
||||
case "inbound_cancel" -> huidingOrderService.cancel(partner, data);
|
||||
default -> throw new IllegalArgumentException("慧订不支持重放动作:" + action);
|
||||
};
|
||||
}
|
||||
|
||||
private Map<String, Object> replayHuanqi(String partner, String action, String rawBody) {
|
||||
if (StrUtil.isBlank(partner)) {
|
||||
throw new IllegalArgumentException("缺少下游伙伴,无法重放环启入站");
|
||||
}
|
||||
JSONObject payload = JSONUtil.parseObj(rawBody);
|
||||
Map<String, Object> resp = new LinkedHashMap<>(4);
|
||||
if ("inbound_check".equals(action)) {
|
||||
R<Map<String, Object>> r = hotelOrderService.checkBookableForDownstream(payload);
|
||||
if (r != null && r.getCode() == 0) {
|
||||
resp.put("code", 200);
|
||||
resp.put("msg", "success");
|
||||
resp.put("data", r.getData());
|
||||
}
|
||||
else {
|
||||
resp.put("code", 500);
|
||||
resp.put("msg", r == null ? "校验失败" : r.getMsg());
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
if ("inbound_push".equals(action)) {
|
||||
R<Map<String, Object>> r = huanqiOrderService.applyPayload(partner, payload);
|
||||
if (r != null && r.getCode() == 0) {
|
||||
resp.put("code", 200);
|
||||
resp.put("msg", "success");
|
||||
resp.put("data", r.getData());
|
||||
}
|
||||
else {
|
||||
resp.put("code", 500);
|
||||
resp.put("msg", r == null ? "处理失败" : r.getMsg());
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
throw new IllegalArgumentException("环启不支持重放动作:" + action);
|
||||
}
|
||||
|
||||
private String resolveProtocol(String partner, String url, String action) {
|
||||
if (StrUtil.isNotBlank(partner) && !"unknown".equalsIgnoreCase(partner)) {
|
||||
HotelDownstreamProperties p = channelConfigService.resolveDownstreamRuntime(partner);
|
||||
if (p != null && StrUtil.isNotBlank(p.getProtocol())) {
|
||||
return SmHotelDownstreamProtocol.normalize(p.getProtocol());
|
||||
}
|
||||
}
|
||||
if (urlContains(url, "shengnuo") || "inbound_timeout".equals(action)) {
|
||||
return SmHotelDownstreamProtocol.SHENGNUO;
|
||||
}
|
||||
if (urlContains(url, "huiding") || "inbound_notify".equals(action)) {
|
||||
return SmHotelDownstreamProtocol.HUIDING;
|
||||
}
|
||||
if (urlContains(url, "/third/party/api/order")) {
|
||||
return SmHotelDownstreamProtocol.HUANQI;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String peekShengnuoPartner(String rawBody) {
|
||||
try {
|
||||
JSONObject body = JSONUtil.parseObj(rawBody);
|
||||
String appId = StrUtil.blankToDefault(body.getStr("appid"), body.getStr("appId"));
|
||||
if (StrUtil.isBlank(appId)) {
|
||||
return null;
|
||||
}
|
||||
for (String code : channelConfigService.listEnabledDownstreamPartners()) {
|
||||
HotelDownstreamProperties p = channelConfigService.resolveDownstreamRuntime(code);
|
||||
if (p == null || !SmHotelDownstreamProtocol.isShengnuo(p.getProtocol())) {
|
||||
continue;
|
||||
}
|
||||
if (appId.equals(ShengnuoDownstreamHotelAdapter.resolveAppId(p))) {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ignored) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static JSONObject parseHuidingData(JSONObject envelope) {
|
||||
Object data = envelope.get("data");
|
||||
if (data instanceof JSONObject jo) {
|
||||
return jo;
|
||||
}
|
||||
String s = envelope.getStr("data");
|
||||
if (StrUtil.isBlank(s)) {
|
||||
s = data == null ? null : String.valueOf(data);
|
||||
}
|
||||
if (StrUtil.isBlank(s)) {
|
||||
throw new IllegalArgumentException("慧订报文 data 为空");
|
||||
}
|
||||
return JSONUtil.parseObj(s);
|
||||
}
|
||||
|
||||
private static boolean urlContains(String url, String part) {
|
||||
return StrUtil.isNotBlank(url) && url.toLowerCase().contains(part.toLowerCase());
|
||||
}
|
||||
|
||||
private static Map<String, Object> failResp(String msg) {
|
||||
Map<String, Object> m = new LinkedHashMap<>(6);
|
||||
m.put("code", 1);
|
||||
m.put("success", false);
|
||||
m.put("msg", StrUtil.blankToDefault(msg, "重放失败"));
|
||||
m.put("status", "error");
|
||||
m.put("data", Map.of());
|
||||
return m;
|
||||
}
|
||||
|
||||
private static Integer extractCode(Map<String, Object> resp) {
|
||||
if (resp == null || resp.get("code") == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Integer.parseInt(String.valueOf(resp.get("code")));
|
||||
}
|
||||
catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String extractMsg(Map<String, Object> resp) {
|
||||
if (resp == null) {
|
||||
return null;
|
||||
}
|
||||
Object msg = resp.get("msg");
|
||||
if (msg == null) {
|
||||
msg = resp.get("message");
|
||||
}
|
||||
return msg == null ? null : String.valueOf(msg);
|
||||
}
|
||||
}
|
||||
+6
-4
@@ -2210,7 +2210,7 @@ public class SmHotelMidServiceImpl implements SmHotelMidService {
|
||||
fillDailyRemain(saved);
|
||||
downstreamAutoPushService.scheduleAfterHotelChanged(hotelId);
|
||||
availabilityNotifyService.scheduleCheckAfterHotelChanged(hotelId);
|
||||
return R.ok(saved);
|
||||
return R.ok(saved, "已保存," + downstreamAutoPushService.autoPushScheduleHint());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -2226,7 +2226,7 @@ public class SmHotelMidServiceImpl implements SmHotelMidService {
|
||||
downstreamAutoPushService.scheduleAfterHotelChanged(hotelId);
|
||||
availabilityNotifyService.scheduleCheckAfterHotelChanged(hotelId);
|
||||
}
|
||||
return R.ok(ok);
|
||||
return R.ok(ok, ok ? "已删除," + downstreamAutoPushService.autoPushScheduleHint() : null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -2347,8 +2347,10 @@ public class SmHotelMidServiceImpl implements SmHotelMidService {
|
||||
data.put("skippedExist", skippedExist);
|
||||
data.put("skippedStockLtSold", skippedStock);
|
||||
String msg = fillMode
|
||||
? String.format("已补齐 %d 天行,跳过已有 %d", toInsert.size(), skippedExist)
|
||||
: String.format("已调整 %d 行,新补齐 %d,配额不足跳过 %d", toUpdate.size(), toInsert.size(), skippedStock);
|
||||
? String.format("已补齐 %d 天行,跳过已有 %d;%s", toInsert.size(), skippedExist,
|
||||
downstreamAutoPushService.autoPushScheduleHint())
|
||||
: String.format("已调整 %d 行,新补齐 %d,配额不足跳过 %d;%s", toUpdate.size(), toInsert.size(), skippedStock,
|
||||
downstreamAutoPushService.autoPushScheduleHint());
|
||||
downstreamAutoPushService.scheduleAfterHotelChanged(hotelId);
|
||||
availabilityNotifyService.scheduleCheckAfterHotelChanged(hotelId);
|
||||
return R.ok(data, msg);
|
||||
|
||||
+251
-9
@@ -1,6 +1,8 @@
|
||||
package com.bsd.pjcloud.supermarket.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONArray;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
@@ -11,12 +13,14 @@ import com.bsd.pjcloud.supermarket.constant.SmHotelStayDates;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelEntity;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelOrderEntity;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelProductEntity;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelRoomEntity;
|
||||
import com.bsd.pjcloud.supermarket.integration.downstream.DownstreamHotelClient;
|
||||
import com.bsd.pjcloud.supermarket.integration.downstream.shengnuo.ShengnuoDownstreamHotelAdapter;
|
||||
import com.bsd.pjcloud.supermarket.integration.downstream.shengnuo.ShengnuoIdMappingCache;
|
||||
import com.bsd.pjcloud.supermarket.mapper.SmHotelMapper;
|
||||
import com.bsd.pjcloud.supermarket.mapper.SmHotelOrderMapper;
|
||||
import com.bsd.pjcloud.supermarket.mapper.SmHotelProductMapper;
|
||||
import com.bsd.pjcloud.supermarket.mapper.SmHotelRoomMapper;
|
||||
import com.bsd.pjcloud.supermarket.service.SmHotelChannelConfigService;
|
||||
import com.bsd.pjcloud.supermarket.service.SmHotelOrderService;
|
||||
import com.bsd.pjcloud.supermarket.service.SmHotelShengnuoOrderService;
|
||||
@@ -28,8 +32,13 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 圣诺订单:入站落库并订上游;成功后 orderChange=已接单(2);取消成功后 orderChange=已取消(5)。
|
||||
@@ -49,6 +58,8 @@ public class SmHotelShengnuoOrderServiceImpl implements SmHotelShengnuoOrderServ
|
||||
|
||||
private static final DateTimeFormatter DAY = DateTimeFormatter.ISO_LOCAL_DATE;
|
||||
|
||||
private static final Pattern BREAKFAST_IN_PLAN = Pattern.compile("含\\s*(\\d+)\\s*份");
|
||||
|
||||
private final SmHotelChannelConfigService channelConfigService;
|
||||
|
||||
private final SmHotelOrderService hotelOrderService;
|
||||
@@ -59,6 +70,8 @@ public class SmHotelShengnuoOrderServiceImpl implements SmHotelShengnuoOrderServ
|
||||
|
||||
private final SmHotelProductMapper productMapper;
|
||||
|
||||
private final SmHotelRoomMapper roomMapper;
|
||||
|
||||
private final DownstreamHotelClient downstreamClient;
|
||||
|
||||
private final ShengnuoIdMappingCache idMappingCache;
|
||||
@@ -67,13 +80,14 @@ public class SmHotelShengnuoOrderServiceImpl implements SmHotelShengnuoOrderServ
|
||||
|
||||
public SmHotelShengnuoOrderServiceImpl(SmHotelChannelConfigService channelConfigService,
|
||||
SmHotelOrderService hotelOrderService, SmHotelOrderMapper orderMapper, SmHotelMapper hotelMapper,
|
||||
SmHotelProductMapper productMapper, DownstreamHotelClient downstreamClient,
|
||||
SmHotelProductMapper productMapper, SmHotelRoomMapper roomMapper, DownstreamHotelClient downstreamClient,
|
||||
ShengnuoIdMappingCache idMappingCache, ShengnuoDownstreamHotelAdapter shengnuoAdapter) {
|
||||
this.channelConfigService = channelConfigService;
|
||||
this.hotelOrderService = hotelOrderService;
|
||||
this.orderMapper = orderMapper;
|
||||
this.hotelMapper = hotelMapper;
|
||||
this.productMapper = productMapper;
|
||||
this.roomMapper = roomMapper;
|
||||
this.downstreamClient = downstreamClient;
|
||||
this.idMappingCache = idMappingCache;
|
||||
this.shengnuoAdapter = shengnuoAdapter;
|
||||
@@ -224,19 +238,46 @@ public class SmHotelShengnuoOrderServiceImpl implements SmHotelShengnuoOrderServ
|
||||
long nsHotelId = toLong(body.get("hotel_id"));
|
||||
long nsRoomId = toLong(body.get("room_type_id"));
|
||||
long nsPlanId = toLong(body.get("price_plan_id"));
|
||||
Long localHotelId = idMappingCache.localHotelIdByNs(partnerCode, nsHotelId);
|
||||
SmHotelEntity hotel = null;
|
||||
if (localHotelId != null) {
|
||||
hotel = hotelMapper.selectById(localHotelId);
|
||||
if (nsHotelId <= 0) {
|
||||
m.error = "hotel_id 不能为空";
|
||||
return m;
|
||||
}
|
||||
cn.hutool.json.JSONObject map = localHotelId == null ? null
|
||||
: idMappingCache.getByLocal(partnerCode, localHotelId);
|
||||
String mapPartner = partnerCode;
|
||||
Long localHotelId = null;
|
||||
String[] hit = idMappingCache.resolveLocalByNs(partnerCode, nsHotelId);
|
||||
if (hit != null) {
|
||||
mapPartner = hit[0];
|
||||
try {
|
||||
localHotelId = Long.parseLong(hit[1].trim());
|
||||
}
|
||||
catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
SmHotelEntity hotel = localHotelId == null ? null : hotelMapper.selectById(localHotelId);
|
||||
JSONObject map = localHotelId == null ? null : idMappingCache.getByLocal(mapPartner, localHotelId);
|
||||
// Redis 映射缺失/过期:按 getInfo(eid) 回查本地酒店并重建映射
|
||||
if (hotel == null) {
|
||||
m.error = "未找到本地酒店(请先推送资源建立映射)nsHotelId=" + nsHotelId;
|
||||
ResolvedHotel recovered = recoverHotelMapping(partnerCode, nsHotelId, body);
|
||||
if (recovered != null) {
|
||||
hotel = recovered.hotel;
|
||||
map = recovered.map;
|
||||
mapPartner = recovered.mapPartner;
|
||||
log.info("|{}|入站映射已重建|nsHotelId={}|localHotelId={}|partner={}|", TAG, nsHotelId, hotel.getId(),
|
||||
mapPartner);
|
||||
}
|
||||
}
|
||||
if (hotel == null) {
|
||||
m.error = "未找到本地酒店(请先推送资源建立映射,或核对外部酒店ID/sHotelId)nsHotelId=" + nsHotelId;
|
||||
return m;
|
||||
}
|
||||
String roomType = idMappingCache.roomTypeByNs(map, nsRoomId);
|
||||
if (StrUtil.isBlank(roomType)) {
|
||||
roomType = matchLocalRoomType(hotel.getId(), nsRoomId, StrUtil.trim(str(body.get("room_type"))), map);
|
||||
}
|
||||
String productCode = idMappingCache.productByNsPlan(map, nsPlanId);
|
||||
if (StrUtil.isBlank(productCode)) {
|
||||
productCode = matchLocalProduct(hotel.getId(), roomType, body, nsPlanId, map, mapPartner, nsHotelId);
|
||||
}
|
||||
if (StrUtil.isBlank(productCode) && StrUtil.isNotBlank(roomType)) {
|
||||
SmHotelProductEntity one = productMapper.selectOne(Wrappers.<SmHotelProductEntity>lambdaQuery()
|
||||
.eq(SmHotelProductEntity::getHotelId, hotel.getId())
|
||||
@@ -247,7 +288,8 @@ public class SmHotelShengnuoOrderServiceImpl implements SmHotelShengnuoOrderServ
|
||||
}
|
||||
}
|
||||
if (StrUtil.isBlank(productCode)) {
|
||||
m.error = "未匹配本地产品 planId=" + nsPlanId + " roomId=" + nsRoomId;
|
||||
m.error = "未匹配本地产品 planId=" + nsPlanId + " roomId=" + nsRoomId
|
||||
+ "(请重新推该酒店资源以刷新房型/计划映射)";
|
||||
return m;
|
||||
}
|
||||
SmHotelProductEntity product = productMapper.selectOne(Wrappers.<SmHotelProductEntity>lambdaQuery()
|
||||
@@ -286,6 +328,206 @@ public class SmHotelShengnuoOrderServiceImpl implements SmHotelShengnuoOrderServ
|
||||
return m;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redis 无映射时:用报文酒店名优先,再对有外部酒店ID的本地店调 getInfo,比对圣诺 hotel id 后重建映射。
|
||||
*/
|
||||
private ResolvedHotel recoverHotelMapping(String partnerCode, long nsHotelId, JSONObject body) {
|
||||
HotelDownstreamProperties ds = channelConfigService.resolveDownstreamRuntime(partnerCode);
|
||||
if (ds == null || !SmHotelDownstreamProtocol.isShengnuo(ds.getProtocol())) {
|
||||
return null;
|
||||
}
|
||||
List<SmHotelEntity> candidates = buildRecoverCandidates(StrUtil.trim(str(body.get("hotel_name"))));
|
||||
for (SmHotelEntity h : candidates) {
|
||||
if (h == null || h.getId() == null) {
|
||||
continue;
|
||||
}
|
||||
String eid = StrUtil.blankToDefault(StrUtil.trim(h.getSHotelId()), StrUtil.trim(h.getHotelCode()));
|
||||
if (StrUtil.isBlank(eid)) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
JSONObject infoResp = shengnuoAdapter.getInfo(partnerCode, ds, eid);
|
||||
JSONObject infoData = infoResp == null ? null : infoResp.getJSONObject("data");
|
||||
long id = infoData == null ? 0L : infoData.getLong("id", 0L);
|
||||
if (id != nsHotelId) {
|
||||
continue;
|
||||
}
|
||||
Map<String, Long> rooms = matchRoomsFromGetInfo(h.getId(), infoData.getJSONArray("rooms"));
|
||||
idMappingCache.save(partnerCode, h.getId(), nsHotelId, rooms, Map.of());
|
||||
JSONObject map = idMappingCache.getByLocal(partnerCode, h.getId());
|
||||
ResolvedHotel out = new ResolvedHotel();
|
||||
out.hotel = h;
|
||||
out.map = map;
|
||||
out.mapPartner = partnerCode;
|
||||
return out;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
log.debug("|{}|getInfo 回查失败|hotelId={}|eid={}|{}|", TAG, h.getId(), eid, ex.getMessage());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<SmHotelEntity> buildRecoverCandidates(String hotelName) {
|
||||
List<SmHotelEntity> ordered = new ArrayList<>();
|
||||
if (StrUtil.isNotBlank(hotelName)) {
|
||||
List<SmHotelEntity> byName = hotelMapper.selectList(Wrappers.<SmHotelEntity>lambdaQuery()
|
||||
.like(SmHotelEntity::getHotelName, hotelName.length() > 24 ? hotelName.substring(0, 24) : hotelName)
|
||||
.last("LIMIT 20"));
|
||||
if (CollUtil.isNotEmpty(byName)) {
|
||||
ordered.addAll(byName);
|
||||
}
|
||||
}
|
||||
List<SmHotelEntity> withEid = hotelMapper.selectList(Wrappers.<SmHotelEntity>lambdaQuery()
|
||||
.isNotNull(SmHotelEntity::getSHotelId)
|
||||
.ne(SmHotelEntity::getSHotelId, "")
|
||||
.orderByDesc(SmHotelEntity::getUpdateTime)
|
||||
.last("LIMIT 60"));
|
||||
if (CollUtil.isNotEmpty(withEid)) {
|
||||
for (SmHotelEntity h : withEid) {
|
||||
if (h == null || h.getId() == null) {
|
||||
continue;
|
||||
}
|
||||
boolean exists = ordered.stream().anyMatch(x -> x != null && h.getId().equals(x.getId()));
|
||||
if (!exists) {
|
||||
ordered.add(h);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ordered;
|
||||
}
|
||||
|
||||
private Map<String, Long> matchRoomsFromGetInfo(Long hotelId, JSONArray nsRooms) {
|
||||
Map<String, Long> out = new LinkedHashMap<>();
|
||||
if (hotelId == null || nsRooms == null || nsRooms.isEmpty()) {
|
||||
return out;
|
||||
}
|
||||
List<SmHotelRoomEntity> rooms = roomMapper.selectList(Wrappers.<SmHotelRoomEntity>lambdaQuery()
|
||||
.eq(SmHotelRoomEntity::getHotelId, hotelId));
|
||||
Map<String, Long> byElong = new HashMap<>();
|
||||
Map<String, Long> byName = new HashMap<>();
|
||||
for (int i = 0; i < nsRooms.size(); i++) {
|
||||
JSONObject r = nsRooms.getJSONObject(i);
|
||||
if (r == null) {
|
||||
continue;
|
||||
}
|
||||
long id = r.getLong("id", 0L);
|
||||
if (id <= 0) {
|
||||
continue;
|
||||
}
|
||||
String elong = StrUtil.trim(r.getStr("elong_room_type_code"));
|
||||
String name = StrUtil.trim(r.getStr("name"));
|
||||
if (StrUtil.isNotBlank(elong) && !"0000".equals(elong)) {
|
||||
byElong.putIfAbsent(elong, id);
|
||||
}
|
||||
if (StrUtil.isNotBlank(name)) {
|
||||
byName.putIfAbsent(name, id);
|
||||
}
|
||||
}
|
||||
for (SmHotelRoomEntity room : rooms) {
|
||||
if (room == null || StrUtil.isBlank(room.getRoomTypeCode())) {
|
||||
continue;
|
||||
}
|
||||
Long nsId = byElong.get(room.getRoomTypeCode());
|
||||
if (nsId == null && StrUtil.isNotBlank(room.getRoomTypeName())) {
|
||||
nsId = byName.get(StrUtil.trim(room.getRoomTypeName()));
|
||||
}
|
||||
if (nsId != null) {
|
||||
out.put(room.getRoomTypeCode(), nsId);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** 映射无房型时:用 getInfo 反查结果或报文房型名兜底 */
|
||||
private String matchLocalRoomType(Long hotelId, long nsRoomId, String roomTypeName, JSONObject map) {
|
||||
if (map != null && nsRoomId > 0) {
|
||||
for (Map.Entry<String, Long> e : idMappingCache.rooms(map).entrySet()) {
|
||||
if (e.getValue() != null && e.getValue() == nsRoomId) {
|
||||
return e.getKey();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (StrUtil.isBlank(roomTypeName) || hotelId == null) {
|
||||
return null;
|
||||
}
|
||||
SmHotelRoomEntity room = roomMapper.selectOne(Wrappers.<SmHotelRoomEntity>lambdaQuery()
|
||||
.eq(SmHotelRoomEntity::getHotelId, hotelId)
|
||||
.eq(SmHotelRoomEntity::getRoomTypeName, roomTypeName)
|
||||
.last("LIMIT 1"));
|
||||
return room == null ? null : room.getRoomTypeCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* 计划映射缺失时:按房型 + 早餐份数(报文 price_plan 如「含2份早餐」)匹配本地产品,并回写 plan 映射。
|
||||
*/
|
||||
private String matchLocalProduct(Long hotelId, String roomType, JSONObject body, long nsPlanId, JSONObject map,
|
||||
String mapPartner, long nsHotelId) {
|
||||
if (hotelId == null) {
|
||||
return null;
|
||||
}
|
||||
var qw = Wrappers.<SmHotelProductEntity>lambdaQuery().eq(SmHotelProductEntity::getHotelId, hotelId);
|
||||
if (StrUtil.isNotBlank(roomType)) {
|
||||
qw.eq(SmHotelProductEntity::getRoomTypeCode, roomType);
|
||||
}
|
||||
List<SmHotelProductEntity> list = productMapper.selectList(qw);
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return null;
|
||||
}
|
||||
Integer breakfast = parseBreakfast(str(body.get("price_plan")));
|
||||
SmHotelProductEntity hit = null;
|
||||
if (breakfast != null) {
|
||||
for (SmHotelProductEntity p : list) {
|
||||
int meal = p.getMealCount() == null ? 0 : Math.max(0, p.getMealCount());
|
||||
if (meal == breakfast) {
|
||||
hit = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hit == null && list.size() == 1) {
|
||||
hit = list.get(0);
|
||||
}
|
||||
if (hit == null) {
|
||||
return null;
|
||||
}
|
||||
if (nsPlanId > 0 && StrUtil.isNotBlank(mapPartner)) {
|
||||
Map<String, Long> rooms = idMappingCache.rooms(map);
|
||||
Map<String, Long> plans = new HashMap<>(idMappingCache.plans(map));
|
||||
plans.put(hit.getProductCode(), nsPlanId);
|
||||
idMappingCache.save(mapPartner, hotelId, nsHotelId, rooms, plans);
|
||||
}
|
||||
return hit.getProductCode();
|
||||
}
|
||||
|
||||
private static Integer parseBreakfast(String pricePlan) {
|
||||
if (StrUtil.isBlank(pricePlan)) {
|
||||
return null;
|
||||
}
|
||||
String s = pricePlan.trim();
|
||||
if (s.contains("无早") || s.contains("不含早")) {
|
||||
return 0;
|
||||
}
|
||||
Matcher m = BREAKFAST_IN_PLAN.matcher(s);
|
||||
if (m.find()) {
|
||||
try {
|
||||
return Integer.parseInt(m.group(1));
|
||||
}
|
||||
catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
if (s.contains("含早")) {
|
||||
return 1;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static final class ResolvedHotel {
|
||||
SmHotelEntity hotel;
|
||||
JSONObject map;
|
||||
String mapPartner;
|
||||
}
|
||||
|
||||
private void pushOrderChange(String partnerCode, String orderId, int status) {
|
||||
if (StrUtil.isBlank(partnerCode) || StrUtil.isBlank(orderId)) {
|
||||
return;
|
||||
|
||||
Reference in new issue
Block a user