酒店
This commit is contained in:
1 parent
14c5856eb1
commit
03757353de
6 files changed
+126
-20
No files matched your search
+4
@@ -27,6 +27,10 @@ public class SmHotelSyncLogEntity extends Model<SmHotelSyncLogEntity> {
|
||||
@Schema(description = "MANUAL / XXL / CRON")
|
||||
private String triggerType;
|
||||
|
||||
/** 上游渠道 sheet_name,或下游全量推时的 partner_code;本地滚加为 local */
|
||||
@Schema(description = "渠道/数据来源")
|
||||
private String dataSource;
|
||||
|
||||
@Schema(description = "RUNNING / SUCCESS / FAILED")
|
||||
private String status;
|
||||
|
||||
|
||||
+9
-3
@@ -149,10 +149,16 @@ public class SmHotelMidController {
|
||||
@GetMapping("/mid/pushScope/preview")
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_smHotel_sync')")
|
||||
public R<Map<String, Object>> previewPushScope(@RequestParam("partnerCode") String partnerCode) {
|
||||
List<Long> ids = channelDeptService.listHotelIdsForDownstreamPartner(partnerCode);
|
||||
Map<String, Object> data = new LinkedHashMap<>(4);
|
||||
List<Long> raw = channelDeptService.listHotelIdsForDownstreamPartner(partnerCode);
|
||||
int rawCount = raw == null ? 0 : raw.size();
|
||||
// 与全量推同一套过滤:圣诺未填 eid 的不计入可推数量
|
||||
List<Long> ids = syncService.filterDownstreamPushHotelIds(partnerCode, raw);
|
||||
int pushCount = ids == null ? 0 : ids.size();
|
||||
Map<String, Object> data = new LinkedHashMap<>(6);
|
||||
data.put("partnerCode", partnerCode);
|
||||
data.put("hotelCount", ids == null ? 0 : ids.size());
|
||||
data.put("hotelCount", pushCount);
|
||||
data.put("rawHotelCount", rawCount);
|
||||
data.put("excludedNoEid", Math.max(0, rawCount - pushCount));
|
||||
return R.ok(data);
|
||||
}
|
||||
|
||||
|
||||
+5
@@ -50,6 +50,11 @@ public interface SmHotelSyncService {
|
||||
*/
|
||||
R<SmHotelSyncLogEntity> startDownstreamScopePushAsync(String partnerCode, String triggerType);
|
||||
|
||||
/**
|
||||
* 下游全量推前过滤酒店 ID(圣诺:去掉未填 sHotelId/eid 的;其它协议原样返回)。
|
||||
*/
|
||||
java.util.List<Long> filterDownstreamPushHotelIds(String partnerCode, java.util.List<Long> hotelIds);
|
||||
|
||||
/**
|
||||
* 根据某次同步日志的 failDetail,只重试失败酒店(支持 DETAIL_PULL / PRICE_ROLL)。
|
||||
*/
|
||||
|
||||
+36
-13
@@ -552,6 +552,7 @@ public class SmHotelMidServiceImpl implements SmHotelMidService {
|
||||
/**
|
||||
* 可售推 / 关房推 / 纯跳过(总览 §5.1)。
|
||||
* SKIP:无酒店或无产品;CLOSE:有产品但下架或窗口无日历;SELL:上架+有产品+窗口有日历。
|
||||
* 圣诺:未填外部酒店ID(sHotelId/eid) 不调对方接口;关房仅在已有 ns 映射时可继续。
|
||||
*/
|
||||
private String resolveDownstreamPushKind(SmHotelEntity hotel, String partnerCode) {
|
||||
if (hotel == null) {
|
||||
@@ -570,7 +571,15 @@ public class SmHotelMidServiceImpl implements SmHotelMidService {
|
||||
.ge(SmHotelDailyEntity::getStayDate, window[0])
|
||||
.le(SmHotelDailyEntity::getStayDate, window[1]));
|
||||
boolean hasDaily = dailyCount != null && dailyCount > 0;
|
||||
if (offline || !hasDaily) {
|
||||
boolean needClose = offline || !hasDaily;
|
||||
if (isShengnuoPartner(partnerCode) && StrUtil.isBlank(StrUtil.trim(hotel.getSHotelId()))) {
|
||||
// 可售推必须 getInfo(eid),无 eid 直接跳过;关房仅 Redis 已有 nsHotelId 时可不调 getInfo
|
||||
if (needClose && hasShengnuoNsMapping(partnerCode, hotelId)) {
|
||||
return SmHotelDownstreamPushKind.CLOSE;
|
||||
}
|
||||
return SmHotelDownstreamPushKind.SKIP;
|
||||
}
|
||||
if (needClose) {
|
||||
return SmHotelDownstreamPushKind.CLOSE;
|
||||
}
|
||||
return SmHotelDownstreamPushKind.SELL;
|
||||
@@ -585,9 +594,26 @@ public class SmHotelMidServiceImpl implements SmHotelMidService {
|
||||
if (productCount == null || productCount <= 0) {
|
||||
return "无产品,无法关房也无法售卖,跳过推送(摘牌前请先关房)";
|
||||
}
|
||||
if (isShengnuoPartner(partnerCode) && StrUtil.isBlank(StrUtil.trim(hotel.getSHotelId()))) {
|
||||
return "未填外部酒店ID(sHotelId/eid),跳过圣诺推送(禁止用酒店编码冒充)";
|
||||
}
|
||||
return "跳过推送";
|
||||
}
|
||||
|
||||
private boolean isShengnuoPartner(String partnerCode) {
|
||||
HotelDownstreamProperties ds = channelConfigService.resolveDownstreamRuntime(partnerCode);
|
||||
return ds != null && SmHotelDownstreamProtocol.isShengnuo(ds.getProtocol());
|
||||
}
|
||||
|
||||
/** 圣诺本地↔nsHotelId 缓存是否已存在(关房可不依赖 eid) */
|
||||
private boolean hasShengnuoNsMapping(String partnerCode, Long hotelId) {
|
||||
if (StrUtil.isBlank(partnerCode) || hotelId == null) {
|
||||
return false;
|
||||
}
|
||||
cn.hutool.json.JSONObject cached = shengnuoIdMappingCache.getByLocal(partnerCode, hotelId);
|
||||
return cached != null && cached.getLong("nsHotelId", 0L) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关房推:环启推 dailyStatus=0;慧订推空价态窗口(对方打满房)。
|
||||
* @param onlyProducts 非空时只关这些产品(环启);慧订忽略,整窗清空
|
||||
@@ -626,17 +652,13 @@ public class SmHotelMidServiceImpl implements SmHotelMidService {
|
||||
|
||||
/**
|
||||
* 圣诺可售推:getInfo(eid) → pushHotelPlan → pushHotelPrice(15 天切片)。
|
||||
* eid 优先 sHotelId(艺龙同程 id),否则 hotelCode。
|
||||
* eid 仅用 sHotelId(艺龙同程 id),未填则上层应已 SKIP,禁止用 hotelCode 冒充。
|
||||
*/
|
||||
private Map<String, Object> pushHotelToShengnuo(SmHotelEntity hotel, String partnerCode) {
|
||||
Long hotelId = hotel.getId();
|
||||
String sHotelId = StrUtil.trim(hotel.getSHotelId());
|
||||
String eid = StrUtil.blankToDefault(sHotelId, StrUtil.trim(hotel.getHotelCode()));
|
||||
String eid = StrUtil.trim(hotel.getSHotelId());
|
||||
if (StrUtil.isBlank(eid)) {
|
||||
throw new IllegalArgumentException("圣诺推送缺少 eid:请在酒店资料填写「外部酒店ID(sHotelId)=艺龙同程id」 hotelId=" + hotelId);
|
||||
}
|
||||
if (StrUtil.isBlank(sHotelId)) {
|
||||
log.warn("|下游推送[圣诺]|未填 sHotelId,回落 hotelCode 作 eid|hotelId={}|eid={}|", hotelId, eid);
|
||||
throw new IllegalArgumentException("圣诺推送缺少外部酒店ID(sHotelId/eid),已禁止用酒店编码冒充 hotelId=" + hotelId);
|
||||
}
|
||||
downstreamClient.assertPartnerReady(partnerCode);
|
||||
HotelDownstreamProperties ds = channelConfigService.resolveDownstreamRuntime(partnerCode);
|
||||
@@ -646,9 +668,8 @@ public class SmHotelMidServiceImpl implements SmHotelMidService {
|
||||
infoResp = shengnuoAdapter.getInfo(partnerCode, ds, eid);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalArgumentException("圣诺 getInfo 失败(eid=" + eid
|
||||
+ (StrUtil.isBlank(sHotelId) ? ",当前用的是 hotelCode,请改填外部酒店ID/艺龙同程id" : ",请核对圣诺侧是否已建档")
|
||||
+ "):" + ex.getMessage(), ex);
|
||||
throw new IllegalArgumentException("圣诺 getInfo 失败(eid=" + eid + ",请核对外部酒店ID/圣诺侧是否已建档):"
|
||||
+ ex.getMessage(), ex);
|
||||
}
|
||||
cn.hutool.json.JSONObject infoData = infoResp == null ? null : infoResp.getJSONObject("data");
|
||||
if (infoData == null) {
|
||||
@@ -837,9 +858,11 @@ public class SmHotelMidServiceImpl implements SmHotelMidService {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
String eid = StrUtil.blankToDefault(StrUtil.trim(hotel.getSHotelId()), StrUtil.trim(hotel.getHotelCode()));
|
||||
// 无缓存时只能靠真实 eid 调 getInfo;禁止 hotelCode 回落
|
||||
String eid = StrUtil.trim(hotel.getSHotelId());
|
||||
if (StrUtil.isBlank(eid)) {
|
||||
throw new IllegalArgumentException("圣诺关房缺少 eid/映射 hotelId=" + hotel.getId());
|
||||
throw new IllegalArgumentException("圣诺关房缺少外部酒店ID(sHotelId)且无本地映射,跳过调接口 hotelId="
|
||||
+ hotel.getId());
|
||||
}
|
||||
cn.hutool.json.JSONObject infoResp = shengnuoAdapter.getInfo(partnerCode, ds, eid);
|
||||
cn.hutool.json.JSONObject infoData = infoResp == null ? null : infoResp.getJSONObject("data");
|
||||
|
||||
+2
-1
@@ -368,7 +368,8 @@ public class SmHotelShengnuoOrderServiceImpl implements SmHotelShengnuoOrderServ
|
||||
if (h == null || h.getId() == null) {
|
||||
continue;
|
||||
}
|
||||
String eid = StrUtil.blankToDefault(StrUtil.trim(h.getSHotelId()), StrUtil.trim(h.getHotelCode()));
|
||||
// 仅用真实外部酒店ID 调 getInfo,禁止 hotelCode 冒充 eid
|
||||
String eid = StrUtil.trim(h.getSHotelId());
|
||||
if (StrUtil.isBlank(eid)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
+70
-3
@@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.bsd.pjcloud.supermarket.config.HotelDownstreamProperties;
|
||||
import com.bsd.pjcloud.supermarket.config.ShangyouHotelProperties;
|
||||
import com.bsd.pjcloud.supermarket.constant.SmHotelDataSource;
|
||||
import com.bsd.pjcloud.supermarket.constant.SmHotelDownstreamProtocol;
|
||||
import com.bsd.pjcloud.supermarket.dto.SmHotelLocalDailyBatchDTO;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelEntity;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelSyncLogEntity;
|
||||
@@ -132,6 +133,7 @@ public class SmHotelSyncServiceImpl implements SmHotelSyncService {
|
||||
redisTemplate.opsForValue().set(ShangyouHotelApi.REDIS_IMPORT_DATA_SOURCE, source, 2, TimeUnit.DAYS);
|
||||
|
||||
SmHotelSyncLogEntity logEntity = newLog(TYPE_LIST, triggerType);
|
||||
logEntity.setDataSource(source);
|
||||
logEntity.setStage("QUEUED");
|
||||
logEntity.setStageMsg("已提交,数据来源=" + source + ",等待执行…");
|
||||
logEntity.setProgressPct(1);
|
||||
@@ -189,6 +191,7 @@ public class SmHotelSyncServiceImpl implements SmHotelSyncService {
|
||||
return R.failed("已有本地滚加任务进行中(若长时间卡住请在同步日志点「终止」)");
|
||||
}
|
||||
SmHotelSyncLogEntity logEntity = newLog(TYPE_LOCAL_ROLL, triggerType);
|
||||
logEntity.setDataSource(SmHotelDataSource.LOCAL);
|
||||
logEntity.setStage("QUEUED");
|
||||
logEntity.setStageMsg("已提交本地滚加任务…");
|
||||
logEntity.setProgressPct(1);
|
||||
@@ -225,6 +228,7 @@ public class SmHotelSyncServiceImpl implements SmHotelSyncService {
|
||||
|
||||
int maxHotels = limit != null && limit > 0 ? limit : props().getDetailPullMaxHotels();
|
||||
SmHotelSyncLogEntity logEntity = newLog(TYPE_DETAIL, triggerType);
|
||||
logEntity.setDataSource(channel);
|
||||
logEntity.setStage("QUEUED");
|
||||
logEntity.setStageMsg((maxHotels > 0 ? "已提交,最多处理 " + maxHotels + " 家" : "已提交,将拉取")
|
||||
+ "「" + channel + "」启用酒店详情,归属部门=" + deptId + "…");
|
||||
@@ -265,11 +269,27 @@ public class SmHotelSyncServiceImpl implements SmHotelSyncService {
|
||||
if (CollUtil.isEmpty(hotelIds)) {
|
||||
return R.failed("下游「" + partner + "」授权范围内没有上架酒店(请检查渠道部门映射与酒店上架状态)");
|
||||
}
|
||||
int beforeFilter = hotelIds.size();
|
||||
hotelIds = filterShengnuoHotelsRequireEid(partner, hotelIds);
|
||||
int droppedNoEid = beforeFilter - hotelIds.size();
|
||||
if (CollUtil.isEmpty(hotelIds)) {
|
||||
return R.failed(droppedNoEid > 0
|
||||
? "下游「" + partner + "」范围内上架酒店均未填外部酒店ID(sHotelId/eid),已全部过滤,无需推送"
|
||||
: "下游「" + partner + "」授权范围内没有可推送酒店");
|
||||
}
|
||||
SmHotelSyncLogEntity logEntity = newLog(TYPE_DOWNSTREAM_PUSH, triggerType);
|
||||
logEntity.setDataSource(partner);
|
||||
logEntity.setStage("QUEUED");
|
||||
logEntity.setStageMsg("已提交:向下游「" + partner + "」推送 " + hotelIds.size() + " 家酒店(仅本地数据)…");
|
||||
String msg = "已提交:向下游「" + partner + "」推送 " + hotelIds.size() + " 家酒店(仅本地数据)";
|
||||
if (droppedNoEid > 0) {
|
||||
msg += ",已排除无 eid " + droppedNoEid + " 家";
|
||||
}
|
||||
logEntity.setStageMsg(msg + "…");
|
||||
logEntity.setProgressPct(1);
|
||||
logEntity.setLocalCount((long) hotelIds.size());
|
||||
if (droppedNoEid > 0) {
|
||||
logEntity.setSkipCount(droppedNoEid);
|
||||
}
|
||||
syncLogMapper.insert(logEntity);
|
||||
|
||||
Long tenantId = TenantContextHolder.getTenantId();
|
||||
@@ -313,6 +333,7 @@ public class SmHotelSyncServiceImpl implements SmHotelSyncService {
|
||||
}
|
||||
|
||||
SmHotelSyncLogEntity logEntity = newLog(syncType, "RETRY");
|
||||
logEntity.setDataSource(src.getDataSource());
|
||||
logEntity.setStage("QUEUED");
|
||||
logEntity.setStageMsg("重试失败酒店:来源日志 " + sourceLogId + ",共 " + codes.size() + " 家"
|
||||
+ (TYPE_ROLL.equals(syncType) ? "(滚价日=" + resolveRollDay(src) + ")" : ""));
|
||||
@@ -389,9 +410,13 @@ public class SmHotelSyncServiceImpl implements SmHotelSyncService {
|
||||
@Override
|
||||
public Page<SmHotelSyncLogEntity> pageSyncLogs(Page<SmHotelSyncLogEntity> page,
|
||||
SmHotelSyncLogEntity query) {
|
||||
String syncType = query == null ? null : query.getSyncType();
|
||||
String status = query == null ? null : query.getStatus();
|
||||
String dataSource = query == null ? null : SmHotelDataSource.normalize(query.getDataSource()).toLowerCase();
|
||||
return syncLogMapper.selectPage(page, Wrappers.<SmHotelSyncLogEntity>lambdaQuery()
|
||||
.eq(StrUtil.isNotBlank(query.getSyncType()), SmHotelSyncLogEntity::getSyncType, query.getSyncType())
|
||||
.eq(StrUtil.isNotBlank(query.getStatus()), SmHotelSyncLogEntity::getStatus, query.getStatus())
|
||||
.eq(StrUtil.isNotBlank(syncType), SmHotelSyncLogEntity::getSyncType, syncType)
|
||||
.eq(StrUtil.isNotBlank(status), SmHotelSyncLogEntity::getStatus, status)
|
||||
.eq(StrUtil.isNotBlank(dataSource), SmHotelSyncLogEntity::getDataSource, dataSource)
|
||||
.orderByDesc(SmHotelSyncLogEntity::getCreateTime));
|
||||
}
|
||||
|
||||
@@ -793,6 +818,48 @@ public class SmHotelSyncServiceImpl implements SmHotelSyncService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> filterDownstreamPushHotelIds(String partnerCode, List<Long> hotelIds) {
|
||||
return filterShengnuoHotelsRequireEid(partnerCode, hotelIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 圣诺推送前过滤:未填外部酒店ID(sHotelId/eid) 的酒店不进入推送队列,避免无意义调对方 getInfo。
|
||||
* 非圣诺协议原样返回。
|
||||
*/
|
||||
private List<Long> filterShengnuoHotelsRequireEid(String partnerCode, List<Long> hotelIds) {
|
||||
if (CollUtil.isEmpty(hotelIds)) {
|
||||
return hotelIds == null ? List.of() : hotelIds;
|
||||
}
|
||||
HotelDownstreamProperties ds = channelConfigService.resolveDownstreamRuntime(partnerCode);
|
||||
if (ds == null || !SmHotelDownstreamProtocol.isShengnuo(ds.getProtocol())) {
|
||||
return hotelIds;
|
||||
}
|
||||
List<SmHotelEntity> rows = hotelMapper.selectList(Wrappers.<SmHotelEntity>lambdaQuery()
|
||||
.select(SmHotelEntity::getId, SmHotelEntity::getSHotelId)
|
||||
.in(SmHotelEntity::getId, hotelIds));
|
||||
Set<Long> withEid = new java.util.HashSet<>();
|
||||
if (CollUtil.isNotEmpty(rows)) {
|
||||
for (SmHotelEntity h : rows) {
|
||||
if (h != null && h.getId() != null && StrUtil.isNotBlank(StrUtil.trim(h.getSHotelId()))) {
|
||||
withEid.add(h.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
List<Long> kept = new ArrayList<>(withEid.size());
|
||||
for (Long id : hotelIds) {
|
||||
if (id != null && withEid.contains(id)) {
|
||||
kept.add(id);
|
||||
}
|
||||
}
|
||||
int dropped = hotelIds.size() - kept.size();
|
||||
if (dropped > 0) {
|
||||
log.info("|下游全量推送|圣诺推送前过滤无eid|partner={}|before={}|after={}|dropped={}|", partnerCode,
|
||||
hotelIds.size(), kept.size(), dropped);
|
||||
}
|
||||
return kept;
|
||||
}
|
||||
|
||||
/** pushToDownstream 预检跳过时 data.skipped=true */
|
||||
private static boolean isDownstreamPushSkipped(Object data) {
|
||||
if (!(data instanceof Map<?, ?> map)) {
|
||||
|
||||
Reference in new issue
Block a user