酒店优化

This commit is contained in:
zjf committed 2026-09-30 23:55:07 +08:00
1 parent 8cd0f3bc3d
commit 13dea2f2a4
5 files changed
+107 -59

No files matched your search

@@ -82,6 +82,13 @@ public class SmHotelMidController {
return midService.pullFromUpstream(id);
}
@Operation(summary = "仅拉取上游房型(getHotelComplexList,不拉房价)")
@PostMapping("/mid/pullRooms/{id}")
@PreAuthorize("@pms.hasPermission('supermarket_smHotel_edit')")
public R<Map<String, Object>> pullRooms(@PathVariable Long id) {
return midService.pullRoomsFromUpstream(id);
}
@Operation(summary = "推送到下游销售方")
@PostMapping("/mid/push/{id}")
@PreAuthorize("@pms.hasPermission('supermarket_smHotel_push')")
@@ -25,9 +25,7 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.ZSetOperations;
/**
@@ -279,7 +277,7 @@ public class ShangyouHotelClient {
* <ul>
* <li>无关键字:ZRANGE + HMGET,深分页也稳定</li>
* <li>精确 hotelId:HGET O(1)</li>
* <li>模糊关键字:HSCAN 分批,只保留当前页(十几万时比全量装载安全,但首次统计全量匹配会稍慢)</li>
* <li>模糊关键字:ZRANGE 分批 + HMGET 内存过滤(避开 Redisson 与 Spring Data Redis 3.3 的 HSCAN/Cursor 不兼容)</li>
* </ul>
*/
public HotelPage pageHotelBriefs(String keyword, long current, long size, boolean forceRefresh) {
@@ -322,20 +320,24 @@ public class ShangyouHotelClient {
long need = size;
long matched = 0;
List<HotelBrief> page = new ArrayList<>((int) size);
ScanOptions options = ScanOptions.scanOptions().count(1000).build();
try (Cursor<Map.Entry<Object, Object>> cursor = redisTemplate.opsForHash()
.scan(ShangyouHotelApi.REDIS_HOTEL_HASH_KEY, options)) {
while (cursor.hasNext()) {
Map.Entry<Object, Object> e = cursor.next();
String hotelId = String.valueOf(e.getKey());
String hotelName = e.getValue() == null ? "" : String.valueOf(e.getValue());
if (!hotelId.toLowerCase().contains(kwLower) && !hotelName.toLowerCase().contains(kwLower)) {
continue;
final long batch = 500L;
try {
for (long start = 0; start < len; start += batch) {
long end = Math.min(start + batch - 1, len - 1);
Set<String> ids = redisTemplate.opsForZSet()
.range(ShangyouHotelApi.REDIS_HOTEL_ZSET_KEY, start, end);
List<HotelBrief> briefs = loadBriefsByIds(ids);
for (HotelBrief b : briefs) {
String hotelId = b.hotelId() == null ? "" : b.hotelId();
String hotelName = b.hotelName() == null ? "" : b.hotelName();
if (!hotelId.toLowerCase().contains(kwLower) && !hotelName.toLowerCase().contains(kwLower)) {
continue;
}
if (matched >= skip && page.size() < need) {
page.add(b);
}
matched++;
}
if (matched >= skip && page.size() < need) {
page.add(new HotelBrief(hotelId, hotelName));
}
matched++;
}
}
catch (Exception ex) {
@@ -16,6 +16,11 @@ public interface SmHotelMidService {
/** 从上游拉取房型/产品并按加价生成日历,落本地库 */
R<Map<String, Object>> pullFromUpstream(Long hotelId);
/**
* 仅调用 getHotelComplexList 落房型(不拉房价),用于单独验证综合信息接口。
*/
R<Map<String, Object>> pullRoomsFromUpstream(Long hotelId);
/**
* 仅拉取指定日期区间房价(含房型综合若本地无房型),用于「滚一天」;区间建议 1 天,最长按上游 31 天切片。
*/
@@ -165,6 +165,42 @@ public class SmHotelMidServiceImpl implements SmHotelMidService {
return R.ok(result);
}
@Override
@Transactional(rollbackFor = Exception.class)
public R<Map<String, Object>> pullRoomsFromUpstream(Long hotelId) {
SmHotelEntity hotel = requireHotel(hotelId);
String hotelCode = hotel.getHotelCode();
List<String> upstreamTips = new ArrayList<>();
log.info("|酒店拉取房型|开始|hotelId={}|hotelCode={}|", hotelId, hotelCode);
JSONObject complexResp = upstreamClient.getHotelComplexListRaw(Collections.singletonList(hotelCode));
String complexMsg = complexResp.getString("message");
String complexCode = complexResp.getString("code");
JSONArray complexList = asContentArray(complexResp.get("content"));
JSONObject complex = complexList.isEmpty() ? new JSONObject() : complexList.getJSONObject(0);
int upstreamRoomTypeSize = complex.getJSONArray("hotelRoomTypeList") == null ? 0
: complex.getJSONArray("hotelRoomTypeList").size();
if (complex.isEmpty()) {
String tip = StrUtil.blankToDefault(complexMsg, "getHotelComplexList content 为空");
upstreamTips.add(tip);
log.warn("|酒店拉取房型|无数据|hotelCode={}|code={}|message={}|", hotelCode, complexCode, complexMsg);
}
else {
log.info("|酒店拉取房型|综合信息|hotelCode={}|roomTypeSize={}|code={}|message={}|", hotelCode,
upstreamRoomTypeSize, complexCode, complexMsg);
}
applyHotelComplex(hotel, complex);
hotelMapper.updateById(hotel);
int roomCount = upsertRoomsFromComplex(hotel, complex);
Map<String, Object> result = new LinkedHashMap<>(8);
result.put("hotelCode", hotelCode);
result.put("upstreamCode", complexCode);
result.put("upstreamMessage", complexMsg);
result.put("upstreamRoomTypeSize", upstreamRoomTypeSize);
result.put("roomCount", roomCount);
result.put("upstreamTips", upstreamTips);
return R.ok(result);
}
@Override
@Transactional(rollbackFor = Exception.class)
public R<Map<String, Object>> pullPriceRange(Long hotelId, LocalDate startInclusive, LocalDate endExclusive) {
@@ -19,8 +19,6 @@ import com.bsd.pjcloud.common.data.tenant.TenantContextHolder;
import com.bsd.pjcloud.common.security.util.SecurityUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
@@ -31,6 +29,7 @@ import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@@ -318,7 +317,7 @@ public class SmHotelSyncServiceImpl implements SmHotelSyncService {
log.info("|酒店列表同步|完成|logId={}|upstream={}|new={}|offlineMarked={}|costMs={}|", logId, upstreamCount,
newCnt, offlineMarked, done.getCostMs());
}
catch (Exception e) {
catch (Throwable e) {
log.error("|酒店列表同步|失败|logId={}|", logId, e);
failLog(logId, e.getMessage(), System.currentTimeMillis() - t0);
}
@@ -415,7 +414,7 @@ public class SmHotelSyncServiceImpl implements SmHotelSyncService {
done.setUpdateTime(LocalDateTime.now());
syncLogMapper.updateById(done);
}
catch (Exception e) {
catch (Throwable e) {
log.error("|酒店滚价|失败|logId={}|", logId, e);
failLog(logId, e.getMessage(), System.currentTimeMillis() - t0);
}
@@ -523,7 +522,7 @@ public class SmHotelSyncServiceImpl implements SmHotelSyncService {
syncLogMapper.updateById(done);
log.info("|全量详情|完成|logId={}|ok={}|fail={}|skip={}|costMs={}|", logId, ok, fail, skip, done.getCostMs());
}
catch (Exception e) {
catch (Throwable e) {
log.error("|全量详情|失败|logId={}|", logId, e);
failLog(logId, e.getMessage(), System.currentTimeMillis() - t0);
}
@@ -625,7 +624,7 @@ public class SmHotelSyncServiceImpl implements SmHotelSyncService {
syncLogMapper.updateById(done);
log.info("|失败重试|完成|logId={}|type={}|ok={}|fail={}|skip={}|", logId, syncType, ok, fail, skip);
}
catch (Exception e) {
catch (Throwable e) {
log.error("|失败重试|失败|logId={}|", logId, e);
failLog(logId, e.getMessage(), System.currentTimeMillis() - t0);
}
@@ -788,19 +787,21 @@ public class SmHotelSyncServiceImpl implements SmHotelSyncService {
private int markOfflineFromDiff() {
int updated = 0;
int batch = Math.max(50, properties.getDiffScanBatchSize());
List<String> codes = new ArrayList<>(batch);
ScanOptions options = ScanOptions.scanOptions().count(batch).build();
try (Cursor<String> cursor = redisTemplate.opsForSet().scan(ShangyouHotelApi.REDIS_DIFF_OFFLINE_SET, options)) {
while (cursor.hasNext()) {
codes.add(cursor.next());
if (codes.size() >= batch) {
updated += updateStatusByCodes(codes, "OFFLINE");
codes.clear();
}
}
// 不用 SET.scan:Redisson 与 Spring Data Redis 3.3 的 Cursor 不兼容会抛 AbstractMethodError
Set<String> all = redisTemplate.opsForSet().members(ShangyouHotelApi.REDIS_DIFF_OFFLINE_SET);
if (all == null || all.isEmpty()) {
return 0;
}
catch (Exception e) {
log.warn("|酒店停用|扫描 offline 差集失败|{}", e.getMessage());
List<String> codes = new ArrayList<>(batch);
for (String code : all) {
if (StrUtil.isBlank(code)) {
continue;
}
codes.add(code);
if (codes.size() >= batch) {
updated += updateStatusByCodes(codes, "OFFLINE");
codes.clear();
}
}
if (!codes.isEmpty()) {
updated += updateStatusByCodes(codes, "OFFLINE");
@@ -869,26 +870,20 @@ public class SmHotelSyncServiceImpl implements SmHotelSyncService {
List<Map<String, Object>> records = new ArrayList<>((int) size);
if (totalVal > 0) {
long skip = (current - 1) * size;
long matched = 0;
long need = skip + size;
ScanOptions options = ScanOptions.scanOptions().count(Math.max(100, properties.getDiffScanBatchSize()))
.build();
try (Cursor<String> cursor = redisTemplate.opsForSet().scan(key, options)) {
while (cursor.hasNext() && matched < need) {
String hotelId = cursor.next();
if (matched >= skip) {
Map<String, Object> row = new LinkedHashMap<>(4);
row.put("hotelId", hotelId);
row.put("hotelName", upstreamClient.hotelNameOf(hotelId));
row.put("type", "offline".equalsIgnoreCase(type) ? "offline" : "new");
records.add(row);
}
matched++;
Set<String> all = redisTemplate.opsForSet().members(key);
if (all != null && !all.isEmpty()) {
List<String> ordered = new ArrayList<>(all);
int from = (int) Math.min(skip, ordered.size());
int to = (int) Math.min(skip + size, ordered.size());
for (int i = from; i < to; i++) {
String hotelId = ordered.get(i);
Map<String, Object> row = new LinkedHashMap<>(4);
row.put("hotelId", hotelId);
row.put("hotelName", upstreamClient.hotelNameOf(hotelId));
row.put("type", "offline".equalsIgnoreCase(type) ? "offline" : "new");
records.add(row);
}
}
catch (Exception e) {
log.warn("|酒店差集分页|scan 异常|{}", e.getMessage());
}
}
Map<String, Object> result = new LinkedHashMap<>();
result.put("records", records);
@@ -904,19 +899,22 @@ public class SmHotelSyncServiceImpl implements SmHotelSyncService {
int max = limit == null || limit <= 0 ? properties.getAutoImportBatchSize() : limit;
max = Math.min(Math.max(max, 1), 5000);
List<Map<String, Object>> batch = new ArrayList<>();
ScanOptions options = ScanOptions.scanOptions().count(Math.max(100, properties.getDiffScanBatchSize())).build();
try (Cursor<String> cursor = redisTemplate.opsForSet().scan(ShangyouHotelApi.REDIS_DIFF_NEW_SET, options)) {
while (cursor.hasNext() && batch.size() < max) {
String hotelId = cursor.next();
// 不用 SET.scan(Redisson/Spring Data Redis 3.3 Cursor 不兼容)
Set<String> all = redisTemplate.opsForSet().members(ShangyouHotelApi.REDIS_DIFF_NEW_SET);
if (all != null) {
for (String hotelId : all) {
if (batch.size() >= max) {
break;
}
if (StrUtil.isBlank(hotelId)) {
continue;
}
Map<String, Object> row = new HashMap<>(2);
row.put("hotelId", hotelId);
row.put("hotelName", upstreamClient.hotelNameOf(hotelId));
batch.add(row);
}
}
catch (Exception e) {
return R.failed("扫描新增差集失败: " + e.getMessage());
}
if (batch.isEmpty()) {
Map<String, Object> empty = new HashMap<>(2);
empty.put("insertCount", 0);