Merge branch 'master' of https://gitea.zianxin.com/zianxin/biz-api-cloud-zax
This commit is contained in:
commit
2caa7dbff4
6 files changed
+532
-129
No files matched your search
+412
-110
@@ -17,6 +17,7 @@ import com.bsd.pjcloud.supermarket.entity.SmHotelOrderEntity;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelProductEntity;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelUserSearchPrefEntity;
|
||||
import com.bsd.pjcloud.supermarket.mapper.SmHotelDailyMapper;
|
||||
import com.bsd.pjcloud.supermarket.mapper.SmHotelMapper;
|
||||
import com.bsd.pjcloud.supermarket.mapper.SmHotelProductMapper;
|
||||
import com.bsd.pjcloud.supermarket.mapper.SmHotelRoomMapper;
|
||||
import com.bsd.pjcloud.supermarket.mapper.SmHotelUserSearchPrefMapper;
|
||||
@@ -40,7 +41,10 @@ import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
@@ -87,15 +91,24 @@ public class SmHotelAppController {
|
||||
private static final String CANDIDATE_CACHE_OVERFLOW = "OVERFLOW";
|
||||
|
||||
/** 列表页结果短缓存(同条件重复进入/下拉) */
|
||||
private static final String PAGE_CACHE_PREFIX = "sm:hotel:app:page:v7:";
|
||||
private static final String PAGE_CACHE_PREFIX = "sm:hotel:app:page:v13:";
|
||||
|
||||
private static final long PAGE_CACHE_SECONDS = 45L;
|
||||
|
||||
/** 先按区域/关键字缩小酒店集合时的上限,超出则回退 EXISTS */
|
||||
private static final int MAX_SCOPED_HOTEL_IDS = 3000;
|
||||
private static final String CITY_ADCODE_CACHE_PREFIX = "sm:hotel:app:city-adc:v1:";
|
||||
|
||||
private static final long CITY_ADCODE_CACHE_HOURS = 24L;
|
||||
|
||||
/** 城市/关键字等缩小后,在该规模内内存排序(超出则 SQL LIMIT,禁止全表 list) */
|
||||
private static final int MAX_SCOPED_HOTEL_IDS = 8000;
|
||||
|
||||
/** 店名包含搜索最多捞取条数(禁止 ORDER BY 全表 filesort) */
|
||||
private static final int KEYWORD_CONTAINS_FETCH = 80;
|
||||
|
||||
private final SmHotelService hotelService;
|
||||
|
||||
private final SmHotelMapper hotelMapper;
|
||||
|
||||
private final SmHotelMidService midService;
|
||||
|
||||
private final SmHotelOrderService orderService;
|
||||
@@ -146,45 +159,21 @@ public class SmHotelAppController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 筛选项:按列 GROUP BY,避免全表加载酒店实体;districts 仅兜底(App 优先走行政区划接口)。
|
||||
* 筛选项:区域走 sysArea,不再对 canteen_hotel 做全表 GROUP BY(会扫十几秒并和列表抢锁)。
|
||||
*/
|
||||
private Map<String, Object> buildFilterMeta() {
|
||||
Set<String> districts = new LinkedHashSet<>();
|
||||
for (SmHotelEntity h : hotelService.list(onlineHotelWrapper()
|
||||
.select(SmHotelEntity::getDistrict)
|
||||
.isNotNull(SmHotelEntity::getDistrict)
|
||||
.ne(SmHotelEntity::getDistrict, "")
|
||||
.groupBy(SmHotelEntity::getDistrict))) {
|
||||
if (h != null && StrUtil.isNotBlank(h.getDistrict())) {
|
||||
districts.add(h.getDistrict().trim());
|
||||
}
|
||||
}
|
||||
Set<String> bizAreas = new LinkedHashSet<>();
|
||||
for (SmHotelEntity h : hotelService.list(onlineHotelWrapper()
|
||||
.select(SmHotelEntity::getBizArea)
|
||||
.isNotNull(SmHotelEntity::getBizArea)
|
||||
.ne(SmHotelEntity::getBizArea, "")
|
||||
.groupBy(SmHotelEntity::getBizArea))) {
|
||||
.groupBy(SmHotelEntity::getBizArea)
|
||||
.last("LIMIT 300"))) {
|
||||
if (h != null && StrUtil.isNotBlank(h.getBizArea())) {
|
||||
bizAreas.add(h.getBizArea().trim());
|
||||
}
|
||||
}
|
||||
Set<String> facilities = new LinkedHashSet<>(DEFAULT_FACILITY_OPTIONS);
|
||||
for (SmHotelEntity h : hotelService.list(onlineHotelWrapper()
|
||||
.select(SmHotelEntity::getFacilityTags)
|
||||
.isNotNull(SmHotelEntity::getFacilityTags)
|
||||
.ne(SmHotelEntity::getFacilityTags, "")
|
||||
.groupBy(SmHotelEntity::getFacilityTags)
|
||||
.last("LIMIT 2000"))) {
|
||||
if (h == null || StrUtil.isBlank(h.getFacilityTags())) {
|
||||
continue;
|
||||
}
|
||||
for (String t : h.getFacilityTags().split("[,,]")) {
|
||||
if (StrUtil.isNotBlank(t)) {
|
||||
facilities.add(t.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
List<Map<String, Object>> starLevels = new ArrayList<>();
|
||||
STAR_LABEL.forEach((k, v) -> {
|
||||
Map<String, Object> item = new LinkedHashMap<>();
|
||||
@@ -194,7 +183,7 @@ public class SmHotelAppController {
|
||||
});
|
||||
starLevels.sort((a, b) -> Integer.compare((Integer) b.get("value"), (Integer) a.get("value")));
|
||||
Map<String, Object> out = new LinkedHashMap<>();
|
||||
out.put("districts", new ArrayList<>(districts));
|
||||
out.put("districts", List.of());
|
||||
out.put("bizAreas", new ArrayList<>(bizAreas));
|
||||
out.put("starLevels", starLevels);
|
||||
out.put("facilityTags", new ArrayList<>(facilities));
|
||||
@@ -246,69 +235,30 @@ public class SmHotelAppController {
|
||||
return R.ok(cached);
|
||||
}
|
||||
|
||||
var wrapper = baseOnlineListWrapper(keyword, city, district, districtAdcode, bizArea, starList, minScore,
|
||||
facilityList);
|
||||
// 列表不查 intro/carousel 大字段,显著降低 IO 与加密体积
|
||||
wrapper.select(SmHotelEntity::getId, SmHotelEntity::getHotelName, SmHotelEntity::getHotelCode,
|
||||
SmHotelEntity::getAddress, SmHotelEntity::getDistrict, SmHotelEntity::getDistrictAdcode,
|
||||
SmHotelEntity::getBizArea, SmHotelEntity::getStarLevel, SmHotelEntity::getScore,
|
||||
SmHotelEntity::getFacilityTags, SmHotelEntity::getCoverImage, SmHotelEntity::getPhone,
|
||||
SmHotelEntity::getSheetName, SmHotelEntity::getUpstreamStatus, SmHotelEntity::getUpdateTime);
|
||||
|
||||
boolean hasScopeFilter = districtAdcode != null || StrUtil.isNotBlank(district) || StrUtil.isNotBlank(city)
|
||||
|| StrUtil.isNotBlank(keyword) || StrUtil.isNotBlank(bizArea) || !starList.isEmpty()
|
||||
|| minScore != null || !facilityList.isEmpty();
|
||||
List<Long> scopedIds = null;
|
||||
if (hasScopeFilter) {
|
||||
scopedIds = loadScopedHotelIds(keyword, city, district, districtAdcode, bizArea, starList, minScore,
|
||||
facilityList);
|
||||
if (scopedIds != null && scopedIds.isEmpty()) {
|
||||
return R.ok(emptyPage(page));
|
||||
}
|
||||
}
|
||||
|
||||
Page<SmHotelEntity> raw;
|
||||
int candSize = -1;
|
||||
boolean smallScope = scopedIds != null && !scopedIds.isEmpty() && scopedIds.size() <= MAX_SCOPED_HOTEL_IDS;
|
||||
if (smallScope) {
|
||||
List<Long> candidateIds = scopedIds;
|
||||
if (needCap > 1) {
|
||||
List<Long> capHotels = roomMapper.selectDistinctHotelIdsByMinCapacity(needCap);
|
||||
if (capHotels == null || capHotels.isEmpty()) {
|
||||
return R.ok(emptyPage(page));
|
||||
}
|
||||
Set<Long> capSet = new java.util.HashSet<>(capHotels);
|
||||
candidateIds = scopedIds.stream().filter(capSet::contains).toList();
|
||||
if (candidateIds.isEmpty()) {
|
||||
return R.ok(emptyPage(page));
|
||||
}
|
||||
}
|
||||
candSize = candidateIds.size();
|
||||
wrapper.in(SmHotelEntity::getId, candidateIds);
|
||||
if (StrUtil.isNotBlank(keyword)) {
|
||||
// 店名搜索专用路径:禁止「LIKE %kw% + ORDER BY」全表 filesort(搜「小如」会超时变空)
|
||||
raw = pageByHotelNameKeyword(page, keyword.trim(), city, district, districtAdcode, starList);
|
||||
}
|
||||
else {
|
||||
// 全国:上架酒店都出列表;所选日期无日历的在行上标已售罄,不再按可订日历过滤掉
|
||||
applyGuestCapacityFilter(wrapper, needCap);
|
||||
}
|
||||
if (minPrice != null || maxPrice != null) {
|
||||
List<Long> priceHitIds = loadHotelIdsByMinPriceRange(minPrice, maxPrice);
|
||||
if (priceHitIds.isEmpty()) {
|
||||
return R.ok(emptyPage(page));
|
||||
boolean hasArea = districtAdcode != null || StrUtil.isNotBlank(city) || StrUtil.isNotBlank(district);
|
||||
var wrapper = applyListColumns(baseOnlineListWrapper(null, city, district, districtAdcode, bizArea,
|
||||
starList, minScore, facilityList, true));
|
||||
if (hasArea) {
|
||||
applyGuestCapacityFilter(wrapper, needCap);
|
||||
applyMinProductPriceFilter(wrapper, minPrice, maxPrice);
|
||||
}
|
||||
wrapper.last("ORDER BY star_level DESC, id DESC");
|
||||
raw = pageWithoutCount(page, wrapper);
|
||||
int got = raw.getRecords() == null ? 0 : raw.getRecords().size();
|
||||
if (hasArea && got < Math.max(1L, Math.min(page.getSize(), 50L)) && page.getCurrent() <= 1) {
|
||||
String uncodedName = StrUtil.firstNonBlank(city, district);
|
||||
if (StrUtil.isNotBlank(uncodedName)) {
|
||||
fillUncodedHotels(raw, uncodedName, null, bizArea, starList, minScore, facilityList, needCap,
|
||||
minPrice, maxPrice);
|
||||
}
|
||||
}
|
||||
wrapper.in(SmHotelEntity::getId, priceHitIds);
|
||||
}
|
||||
// 五星档次优先,同档按产品标价从高到低(无星级/无价格的靠后)
|
||||
wrapper.last("""
|
||||
ORDER BY IFNULL(star_level, 0) DESC,
|
||||
IFNULL((
|
||||
SELECT MIN(p.sell_price) FROM canteen_hotel_product p
|
||||
WHERE p.del_flag = '0'
|
||||
AND p.hotel_id = canteen_hotel.id
|
||||
AND p.sell_price IS NOT NULL
|
||||
), 0) DESC,
|
||||
id DESC
|
||||
""");
|
||||
raw = pageWithoutCount(page, wrapper);
|
||||
List<Long> ids = raw.getRecords().stream().map(SmHotelEntity::getId).filter(Objects::nonNull).toList();
|
||||
LocalDate priceFrom = stayFrom != null ? stayFrom : LocalDate.now();
|
||||
LocalDate priceToEx = stayToExclusive;
|
||||
@@ -322,9 +272,8 @@ public class SmHotelAppController {
|
||||
.map(h -> toListRow(h, minPriceMap.get(h.getId())))
|
||||
.toList());
|
||||
writePageCache(pageCacheKey, out);
|
||||
log.info("|酒店App|page|current={}|size={}|total={}|scope={}|cand={}|costMs={}|", page.getCurrent(),
|
||||
page.getSize(), raw.getTotal(), scopedIds == null ? -1 : scopedIds.size(), candSize,
|
||||
System.currentTimeMillis() - t0);
|
||||
log.info("|酒店App|page|current={}|size={}|total={}|costMs={}|", page.getCurrent(), page.getSize(),
|
||||
raw.getTotal(), System.currentTimeMillis() - t0);
|
||||
return R.ok(out);
|
||||
}
|
||||
|
||||
@@ -334,6 +283,132 @@ public class SmHotelAppController {
|
||||
return empty;
|
||||
}
|
||||
|
||||
/** 内存分页:语义与 pageWithoutCount 一致,不做 COUNT(*) */
|
||||
private static Page<SmHotelEntity> sliceWithoutCount(Page<SmHotelEntity> page, List<SmHotelEntity> sorted) {
|
||||
long size = Math.max(1L, Math.min(page.getSize(), 50L));
|
||||
long current = Math.max(1L, page.getCurrent());
|
||||
int from = (int) Math.min((current - 1) * size, sorted.size());
|
||||
int to = (int) Math.min(from + size, sorted.size());
|
||||
List<SmHotelEntity> slice = from >= sorted.size() ? List.of() : sorted.subList(from, to);
|
||||
boolean hasMore = sorted.size() > to;
|
||||
long total = (current - 1) * size + slice.size() + (hasMore ? 1 : 0);
|
||||
Page<SmHotelEntity> out = new Page<>(current, size, total);
|
||||
out.setRecords(new ArrayList<>(slice));
|
||||
return out;
|
||||
}
|
||||
|
||||
private static LambdaQueryWrapper<SmHotelEntity> applyListColumns(LambdaQueryWrapper<SmHotelEntity> wrapper) {
|
||||
return wrapper.select(SmHotelEntity::getId, SmHotelEntity::getHotelName, SmHotelEntity::getHotelCode,
|
||||
SmHotelEntity::getAddress, SmHotelEntity::getDistrict, SmHotelEntity::getDistrictAdcode,
|
||||
SmHotelEntity::getBizArea, SmHotelEntity::getStarLevel, SmHotelEntity::getScore,
|
||||
SmHotelEntity::getFacilityTags, SmHotelEntity::getCoverImage, SmHotelEntity::getPhone,
|
||||
SmHotelEntity::getSheetName, SmHotelEntity::getUpstreamStatus, SmHotelEntity::getUpdateTime);
|
||||
}
|
||||
|
||||
private List<SmHotelEntity> loadListRowsInOrder(List<Long> ids) {
|
||||
if (ids == null || ids.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
List<SmHotelEntity> rows = hotelService.list(applyListColumns(Wrappers.<SmHotelEntity>lambdaQuery()
|
||||
.in(SmHotelEntity::getId, ids)));
|
||||
if (rows == null || rows.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
Map<Long, SmHotelEntity> byId = new HashMap<>();
|
||||
for (SmHotelEntity h : rows) {
|
||||
if (h != null && h.getId() != null) {
|
||||
byId.put(h.getId(), h);
|
||||
}
|
||||
}
|
||||
List<SmHotelEntity> ordered = new ArrayList<>(ids.size());
|
||||
for (Long id : ids) {
|
||||
SmHotelEntity h = byId.get(id);
|
||||
if (h != null) {
|
||||
ordered.add(h);
|
||||
}
|
||||
}
|
||||
return ordered;
|
||||
}
|
||||
|
||||
private static boolean inProductMinRange(BigDecimal minSell, BigDecimal minPrice, BigDecimal maxPrice) {
|
||||
if (minSell == null) {
|
||||
return false;
|
||||
}
|
||||
if (minPrice != null && minSell.compareTo(minPrice) < 0) {
|
||||
return false;
|
||||
}
|
||||
return maxPrice == null || minSell.compareTo(maxPrice) <= 0;
|
||||
}
|
||||
|
||||
private Map<Long, BigDecimal> queryProductMinPricesByIds(List<Long> hotelIds) {
|
||||
Map<Long, BigDecimal> out = new HashMap<>();
|
||||
if (hotelIds == null || hotelIds.isEmpty()) {
|
||||
return out;
|
||||
}
|
||||
int batch = 800;
|
||||
for (int i = 0; i < hotelIds.size(); i += batch) {
|
||||
List<Long> part = hotelIds.subList(i, Math.min(i + batch, hotelIds.size()));
|
||||
putHotelMinPrices(out, productMapper.selectMinSellPriceByHotelIds(part));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static void putHotelMinPrices(Map<Long, BigDecimal> out, List<Map<String, Object>> rows) {
|
||||
if (rows == null) {
|
||||
return;
|
||||
}
|
||||
for (Map<String, Object> row : rows) {
|
||||
if (row == null || row.get("hotelId") == null || row.get("minSell") == null) {
|
||||
continue;
|
||||
}
|
||||
out.put(Long.valueOf(String.valueOf(row.get("hotelId"))), new BigDecimal(String.valueOf(row.get("minSell"))));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 起价筛选等价于 MIN(sell_price) 落在区间:有产品、没有更低越界价、且存在不超过上限的价。
|
||||
* 只挂在 LIMIT 分页 SQL 上,禁止全表 GROUP BY。
|
||||
*/
|
||||
private void applyMinProductPriceFilter(LambdaQueryWrapper<SmHotelEntity> wrapper, BigDecimal minPrice,
|
||||
BigDecimal maxPrice) {
|
||||
if (minPrice == null && maxPrice == null) {
|
||||
return;
|
||||
}
|
||||
if (minPrice != null) {
|
||||
wrapper.apply("""
|
||||
NOT EXISTS (
|
||||
SELECT 1 FROM canteen_hotel_product p
|
||||
WHERE p.hotel_id = canteen_hotel.id
|
||||
AND p.del_flag = '0'
|
||||
AND p.sell_price IS NOT NULL
|
||||
AND p.sell_price < {0}
|
||||
LIMIT 1
|
||||
)
|
||||
""", minPrice);
|
||||
wrapper.apply("""
|
||||
EXISTS (
|
||||
SELECT 1 FROM canteen_hotel_product p
|
||||
WHERE p.hotel_id = canteen_hotel.id
|
||||
AND p.del_flag = '0'
|
||||
AND p.sell_price IS NOT NULL
|
||||
LIMIT 1
|
||||
)
|
||||
""");
|
||||
}
|
||||
if (maxPrice != null) {
|
||||
wrapper.apply("""
|
||||
EXISTS (
|
||||
SELECT 1 FROM canteen_hotel_product p
|
||||
WHERE p.hotel_id = canteen_hotel.id
|
||||
AND p.del_flag = '0'
|
||||
AND p.sell_price IS NOT NULL
|
||||
AND p.sell_price <= {0}
|
||||
LIMIT 1
|
||||
)
|
||||
""", maxPrice);
|
||||
}
|
||||
}
|
||||
|
||||
/** 列表只要一页,不做 COUNT(*)(全国 COUNT+EXISTS 会扫全表十几秒) */
|
||||
private Page<SmHotelEntity> pageWithoutCount(Page<SmHotelEntity> page, LambdaQueryWrapper<SmHotelEntity> wrapper) {
|
||||
long size = Math.max(1L, Math.min(page.getSize(), 50L));
|
||||
@@ -349,35 +424,100 @@ public class SmHotelAppController {
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按酒店名搜索:
|
||||
* 1) 前缀匹配(idx_hotel_name + ORDER BY,快);
|
||||
* 2) 无结果再含匹配——SQL 不做 ORDER BY,只 LIMIT,再内存排序(避免超时变空列表)。
|
||||
* 有城市时尽量落到 district_adcode 区间。
|
||||
*/
|
||||
private Page<SmHotelEntity> pageByHotelNameKeyword(Page<SmHotelEntity> page, String keyword, String city,
|
||||
String district, Long districtAdcode, List<Integer> starList) {
|
||||
long size = Math.max(1L, Math.min(page.getSize(), 50L));
|
||||
long current = Math.max(1L, page.getCurrent());
|
||||
int fetch = (int) Math.min(KEYWORD_CONTAINS_FETCH, Math.max(size + 1, size * current + 1));
|
||||
Long adFrom = null;
|
||||
Long adTo = null;
|
||||
Long adcode = districtAdcode;
|
||||
if (adcode == null && StrUtil.isNotBlank(city)) {
|
||||
adcode = resolvePlaceAdcode(city.trim(), true);
|
||||
}
|
||||
if (adcode == null && StrUtil.isNotBlank(district) && StrUtil.isBlank(city)) {
|
||||
adcode = resolvePlaceAdcode(district.trim(), false);
|
||||
}
|
||||
if (adcode != null) {
|
||||
long[] range = HotelAreaAdcodeUtil.areaAdcodeRange(adcode);
|
||||
adFrom = range[0];
|
||||
adTo = range[1];
|
||||
}
|
||||
Collection<Integer> stars = starList == null || starList.isEmpty() ? null : starList;
|
||||
List<SmHotelEntity> rows = hotelMapper.selectAppByNamePrefix(keyword, adFrom, adTo, stars, fetch);
|
||||
if (rows == null || rows.isEmpty()) {
|
||||
// 含匹配绝不带 ORDER BY;有城市时区间小,全国则最多捞 KEYWORD_CONTAINS_FETCH 条后内存排
|
||||
rows = hotelMapper.selectAppByNameContains(keyword, adFrom, adTo, stars, fetch);
|
||||
}
|
||||
if (rows == null) {
|
||||
rows = List.of();
|
||||
}
|
||||
List<SmHotelEntity> sorted = new ArrayList<>(rows);
|
||||
sorted.sort(Comparator
|
||||
.comparing((SmHotelEntity h) -> h.getStarLevel() == null ? 0 : h.getStarLevel())
|
||||
.reversed()
|
||||
.thenComparing((SmHotelEntity h) -> h.getId() == null ? 0L : h.getId(), Comparator.reverseOrder()));
|
||||
int from = (int) Math.min((current - 1) * size, sorted.size());
|
||||
int to = (int) Math.min(from + size, sorted.size());
|
||||
List<SmHotelEntity> slice = from >= sorted.size() ? List.of() : sorted.subList(from, to);
|
||||
boolean hasMore = sorted.size() > to || sorted.size() >= fetch;
|
||||
long total = (current - 1) * size + slice.size() + (hasMore ? 1 : 0);
|
||||
Page<SmHotelEntity> out = new Page<>(current, size, total);
|
||||
out.setRecords(new ArrayList<>(slice));
|
||||
return out;
|
||||
}
|
||||
|
||||
/** 上架 + 区域/关键字/星级等公共条件(不含可售日历、排序) */
|
||||
private LambdaQueryWrapper<SmHotelEntity> baseOnlineListWrapper(String keyword, String city, String district,
|
||||
Long districtAdcode, String bizArea, List<Integer> starList, BigDecimal minScore,
|
||||
List<String> facilityList) {
|
||||
return baseOnlineListWrapper(keyword, city, district, districtAdcode, bizArea, starList, minScore,
|
||||
facilityList, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param namePrefix true=酒店名走前缀(可用 idx_hotel_name);false=含匹配(仅前缀无结果时)
|
||||
*/
|
||||
private LambdaQueryWrapper<SmHotelEntity> baseOnlineListWrapper(String keyword, String city, String district,
|
||||
Long districtAdcode, String bizArea, List<Integer> starList, BigDecimal minScore,
|
||||
List<String> facilityList, boolean namePrefix) {
|
||||
var wrapper = Wrappers.<SmHotelEntity>lambdaQuery()
|
||||
.and(w -> w.isNull(SmHotelEntity::getUpstreamStatus)
|
||||
.or()
|
||||
.ne(SmHotelEntity::getUpstreamStatus, "OFFLINE"))
|
||||
.and(StrUtil.isNotBlank(keyword), w -> w.like(SmHotelEntity::getHotelName, keyword.trim())
|
||||
.or()
|
||||
.like(SmHotelEntity::getAddress, keyword.trim())
|
||||
.or()
|
||||
.like(SmHotelEntity::getDistrict, keyword.trim())
|
||||
.or()
|
||||
.like(SmHotelEntity::getBizArea, keyword.trim())
|
||||
.or()
|
||||
.like(SmHotelEntity::getHotelCode, keyword.trim()))
|
||||
.and(districtAdcode == null && StrUtil.isNotBlank(city),
|
||||
w -> w.like(SmHotelEntity::getAddress, city.trim())
|
||||
.or()
|
||||
.like(SmHotelEntity::getDistrict, city.trim())
|
||||
.or()
|
||||
.like(SmHotelEntity::getHotelName, city.trim()));
|
||||
if (districtAdcode != null) {
|
||||
HotelAreaAdcodeUtil.applyHotelListFilter(wrapper, districtAdcode, district);
|
||||
.ne(SmHotelEntity::getUpstreamStatus, "OFFLINE"));
|
||||
Long adcode = districtAdcode;
|
||||
if (adcode == null && StrUtil.isNotBlank(city)) {
|
||||
adcode = resolvePlaceAdcode(city.trim(), true);
|
||||
}
|
||||
if (adcode != null) {
|
||||
long[] range = HotelAreaAdcodeUtil.areaAdcodeRange(adcode);
|
||||
wrapper.apply("district_adcode >= {0} AND district_adcode < {1}", range[0], range[1]);
|
||||
}
|
||||
else if (StrUtil.isNotBlank(city)) {
|
||||
applyDistrictPrefix(wrapper, city.trim());
|
||||
}
|
||||
else if (StrUtil.isNotBlank(district)) {
|
||||
wrapper.eq(SmHotelEntity::getDistrict, StrUtil.trim(district));
|
||||
}
|
||||
if (StrUtil.isNotBlank(keyword)) {
|
||||
String kw = keyword.trim();
|
||||
if (namePrefix) {
|
||||
wrapper.and(w -> w.likeRight(SmHotelEntity::getHotelName, kw)
|
||||
.or()
|
||||
.eq(SmHotelEntity::getHotelCode, kw));
|
||||
}
|
||||
else {
|
||||
wrapper.and(w -> w.like(SmHotelEntity::getHotelName, kw)
|
||||
.or()
|
||||
.eq(SmHotelEntity::getHotelCode, kw));
|
||||
}
|
||||
}
|
||||
wrapper.like(StrUtil.isNotBlank(bizArea), SmHotelEntity::getBizArea, StrUtil.trim(bizArea))
|
||||
.in(!starList.isEmpty(), SmHotelEntity::getStarLevel, starList)
|
||||
.ge(minScore != null, SmHotelEntity::getScore, minScore);
|
||||
@@ -387,6 +527,168 @@ public class SmHotelAppController {
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
/** 首页不足一页时,只补 district_adcode 为空的老数据,不并进主查询以免拖垮索引 */
|
||||
private void fillUncodedHotels(Page<SmHotelEntity> raw, String placeName, String keyword, String bizArea,
|
||||
List<Integer> starList, BigDecimal minScore, List<String> facilityList, int needCap,
|
||||
BigDecimal minPrice, BigDecimal maxPrice) {
|
||||
int size = (int) Math.max(1L, Math.min(raw.getSize(), 50L));
|
||||
int need = size - (raw.getRecords() == null ? 0 : raw.getRecords().size());
|
||||
if (need <= 0 || StrUtil.isBlank(placeName)) {
|
||||
return;
|
||||
}
|
||||
Set<Long> have = new HashSet<>();
|
||||
if (raw.getRecords() != null) {
|
||||
for (SmHotelEntity h : raw.getRecords()) {
|
||||
if (h != null && h.getId() != null) {
|
||||
have.add(h.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
String name = placeName.trim();
|
||||
String shortName = HotelAreaAdcodeUtil.shortenAreaName(name);
|
||||
var w = applyListColumns(Wrappers.<SmHotelEntity>lambdaQuery()
|
||||
.and(x -> x.isNull(SmHotelEntity::getUpstreamStatus)
|
||||
.or()
|
||||
.ne(SmHotelEntity::getUpstreamStatus, "OFFLINE"))
|
||||
.isNull(SmHotelEntity::getDistrictAdcode)
|
||||
.and(p -> {
|
||||
p.like(SmHotelEntity::getDistrict, name)
|
||||
.or()
|
||||
.like(SmHotelEntity::getHotelName, name)
|
||||
.or()
|
||||
.like(SmHotelEntity::getAddress, name);
|
||||
if (StrUtil.isNotBlank(shortName) && !shortName.equals(name)) {
|
||||
p.or()
|
||||
.like(SmHotelEntity::getDistrict, shortName)
|
||||
.or()
|
||||
.like(SmHotelEntity::getHotelName, shortName)
|
||||
.or()
|
||||
.like(SmHotelEntity::getAddress, shortName);
|
||||
}
|
||||
}));
|
||||
if (StrUtil.isNotBlank(keyword) && !keyword.trim().equals(name)) {
|
||||
String kw = keyword.trim();
|
||||
w.and(x -> x.like(SmHotelEntity::getHotelName, kw).or().like(SmHotelEntity::getHotelCode, kw));
|
||||
}
|
||||
w.like(StrUtil.isNotBlank(bizArea), SmHotelEntity::getBizArea, StrUtil.trim(bizArea))
|
||||
.in(!starList.isEmpty(), SmHotelEntity::getStarLevel, starList)
|
||||
.ge(minScore != null, SmHotelEntity::getScore, minScore);
|
||||
for (String tag : facilityList) {
|
||||
w.like(SmHotelEntity::getFacilityTags, tag);
|
||||
}
|
||||
applyGuestCapacityFilter(w, needCap);
|
||||
applyMinProductPriceFilter(w, minPrice, maxPrice);
|
||||
w.orderByDesc(SmHotelEntity::getStarLevel).orderByDesc(SmHotelEntity::getId).last("LIMIT " + (need + 5));
|
||||
List<SmHotelEntity> extra = hotelService.list(w);
|
||||
if (extra == null || extra.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<SmHotelEntity> merged = new ArrayList<>(raw.getRecords() == null ? List.of() : raw.getRecords());
|
||||
for (SmHotelEntity h : extra) {
|
||||
if (h == null || h.getId() == null || have.contains(h.getId())) {
|
||||
continue;
|
||||
}
|
||||
merged.add(h);
|
||||
have.add(h.getId());
|
||||
if (merged.size() >= size) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
raw.setRecords(merged);
|
||||
raw.setTotal(merged.size() + (merged.size() >= size ? 1 : 0));
|
||||
}
|
||||
|
||||
private static void applyDistrictPrefix(LambdaQueryWrapper<SmHotelEntity> wrapper, String name) {
|
||||
String shortName = HotelAreaAdcodeUtil.shortenAreaName(name);
|
||||
wrapper.and(w -> {
|
||||
w.likeRight(SmHotelEntity::getDistrict, name);
|
||||
if (StrUtil.isNotBlank(shortName) && !shortName.equals(name)) {
|
||||
w.or().likeRight(SmHotelEntity::getDistrict, shortName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 地名 → adcode(缓存 24h)。从已落编码的酒店反推。
|
||||
* @param promoteToCity true=收到市级(出行城市);false=保留区县(搜索框地名)
|
||||
*/
|
||||
private Long resolvePlaceAdcode(String place, boolean promoteToCity) {
|
||||
if (StrUtil.isBlank(place)) {
|
||||
return null;
|
||||
}
|
||||
String name = place.trim();
|
||||
String cacheKey = CITY_ADCODE_CACHE_PREFIX + (promoteToCity ? "c:" : "p:") + name;
|
||||
try {
|
||||
String cached = stringRedisTemplate.opsForValue().get(cacheKey);
|
||||
if ("-".equals(cached)) {
|
||||
return null;
|
||||
}
|
||||
if (StrUtil.isNotBlank(cached)) {
|
||||
return Long.valueOf(cached);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.warn("|酒店App|地名adcode缓存读取失败|{}", e.getMessage());
|
||||
}
|
||||
// 只精确匹配区名,禁止 LIKE 全表扫(否则选城市本身就会卡死)
|
||||
String shortName = HotelAreaAdcodeUtil.shortenAreaName(name);
|
||||
List<SmHotelEntity> rows = hotelService.list(Wrappers.<SmHotelEntity>lambdaQuery()
|
||||
.select(SmHotelEntity::getDistrictAdcode, SmHotelEntity::getDistrict)
|
||||
.isNotNull(SmHotelEntity::getDistrictAdcode)
|
||||
.and(w -> {
|
||||
w.eq(SmHotelEntity::getDistrict, name)
|
||||
.or()
|
||||
.eq(SmHotelEntity::getDistrict, name + "市")
|
||||
.or()
|
||||
.eq(SmHotelEntity::getDistrict, name + "区")
|
||||
.or()
|
||||
.eq(SmHotelEntity::getDistrict, name + "县");
|
||||
if (StrUtil.isNotBlank(shortName) && !shortName.equals(name)) {
|
||||
w.or().eq(SmHotelEntity::getDistrict, shortName);
|
||||
}
|
||||
})
|
||||
.last("LIMIT 20"));
|
||||
Long adcode = pickPlaceAdcode(rows, promoteToCity);
|
||||
try {
|
||||
stringRedisTemplate.opsForValue()
|
||||
.set(cacheKey, adcode == null ? "-" : String.valueOf(adcode), CITY_ADCODE_CACHE_HOURS,
|
||||
TimeUnit.HOURS);
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.warn("|酒店App|地名adcode缓存写入失败|{}", e.getMessage());
|
||||
}
|
||||
return adcode;
|
||||
}
|
||||
|
||||
private static Long pickPlaceAdcode(List<SmHotelEntity> rows, boolean promoteToCity) {
|
||||
if (rows == null || rows.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
Map<Long, Integer> votes = new HashMap<>();
|
||||
for (SmHotelEntity h : rows) {
|
||||
if (h == null || h.getDistrictAdcode() == null) {
|
||||
continue;
|
||||
}
|
||||
long code = promoteToCity ? toCityAdcode(h.getDistrictAdcode()) : h.getDistrictAdcode();
|
||||
votes.merge(code, 1, Integer::sum);
|
||||
}
|
||||
return votes.entrySet()
|
||||
.stream()
|
||||
.max(Map.Entry.comparingByValue())
|
||||
.map(Map.Entry::getKey)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private static long toCityAdcode(long adcode) {
|
||||
if (adcode >= 100000 && adcode % 10000 == 0) {
|
||||
return adcode;
|
||||
}
|
||||
if (adcode >= 100000 && adcode % 100 == 0) {
|
||||
return adcode;
|
||||
}
|
||||
return (adcode / 100) * 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* 先按区域/关键字等缩小酒店 id;过大则返回 null(调用方走 EXISTS,避免超长 IN)。
|
||||
*/
|
||||
|
||||
+6
-2
@@ -99,12 +99,16 @@ public class ShangyouCallbackQueueWorker implements ApplicationRunner {
|
||||
log.warn("|回调队列|非法 taskId={}|", taskId);
|
||||
continue;
|
||||
}
|
||||
boolean ok = callbackTaskService.processQueuedTask(id);
|
||||
int result = callbackTaskService.processQueuedTask(id);
|
||||
if (result == 0) {
|
||||
// 残留 SUCCESS 副本:不限流、不打间隔,尽快排空以免占连接
|
||||
continue;
|
||||
}
|
||||
int interval = Math.max(0, p.getCallbackTaskIntervalMs());
|
||||
if (interval > 0) {
|
||||
Thread.sleep(interval);
|
||||
}
|
||||
if (ok) {
|
||||
if (result > 0) {
|
||||
consecutiveFail = 0;
|
||||
}
|
||||
else {
|
||||
|
||||
+3
@@ -44,6 +44,9 @@ public final class ShangyouHotelApi {
|
||||
/** 回调异步队列(LIST,元素=taskId):房价/房量等普通优先级 */
|
||||
public static final String REDIS_CALLBACK_QUEUE = "supermarket:shangyou:hotel:callback:queue";
|
||||
|
||||
/** 已在队列中的 taskId(防重复 leftPush,避免 SUCCESS 副本每次启动再扫一遍) */
|
||||
public static final String REDIS_CALLBACK_QUEUED = "supermarket:shangyou:hotel:callback:queued";
|
||||
|
||||
/** 回调高优先级队列:订单状态等,Worker 始终先消费本队列 */
|
||||
public static final String REDIS_CALLBACK_QUEUE_HIGH = "supermarket:shangyou:hotel:callback:queue:high";
|
||||
|
||||
|
||||
+58
@@ -4,11 +4,69 @@ import com.bsd.pjcloud.supermarket.entity.SmHotelEntity;
|
||||
import com.bsd.pjcloud.common.data.datascope.PjcloudBaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface SmHotelMapper extends PjcloudBaseMapper<SmHotelEntity> {
|
||||
|
||||
int insertBatch(@Param("list") List<SmHotelEntity> list);
|
||||
|
||||
/**
|
||||
* App 店名前缀搜索:走 idx_hotel_name,可带区划区间;带 ORDER BY 安全(前缀命中集小)。
|
||||
*/
|
||||
@Select("""
|
||||
<script>
|
||||
SELECT id, hotel_name AS hotelName, hotel_code AS hotelCode, address, district,
|
||||
district_adcode AS districtAdcode, biz_area AS bizArea, star_level AS starLevel,
|
||||
score, facility_tags AS facilityTags, cover_image AS coverImage, phone,
|
||||
sheet_name AS sheetName, upstream_status AS upstreamStatus, update_time AS updateTime
|
||||
FROM canteen_hotel FORCE INDEX (idx_hotel_name)
|
||||
WHERE del_flag = '0'
|
||||
AND (upstream_status IS NULL OR upstream_status <> 'OFFLINE')
|
||||
AND (hotel_name LIKE CONCAT(#{keyword}, '%') OR hotel_code = #{keyword})
|
||||
<if test="adFrom != null">
|
||||
AND district_adcode >= #{adFrom} AND district_adcode < #{adTo}
|
||||
</if>
|
||||
<if test="starLevels != null and starLevels.size() > 0">
|
||||
AND star_level IN
|
||||
<foreach collection="starLevels" item="s" open="(" separator="," close=")">#{s}</foreach>
|
||||
</if>
|
||||
ORDER BY IFNULL(star_level, 0) DESC, id DESC
|
||||
LIMIT #{limit}
|
||||
</script>
|
||||
""")
|
||||
List<SmHotelEntity> selectAppByNamePrefix(@Param("keyword") String keyword, @Param("adFrom") Long adFrom,
|
||||
@Param("adTo") Long adTo, @Param("starLevels") Collection<Integer> starLevels,
|
||||
@Param("limit") int limit);
|
||||
|
||||
/**
|
||||
* App 店名包含搜索:禁止 ORDER BY(含匹配 + 排序 = 全表 filesort 超时)。
|
||||
* 先 LIMIT 取候选,由调用方内存排序。有 adcode 时扫描范围极小。
|
||||
*/
|
||||
@Select("""
|
||||
<script>
|
||||
SELECT id, hotel_name AS hotelName, hotel_code AS hotelCode, address, district,
|
||||
district_adcode AS districtAdcode, biz_area AS bizArea, star_level AS starLevel,
|
||||
score, facility_tags AS facilityTags, cover_image AS coverImage, phone,
|
||||
sheet_name AS sheetName, upstream_status AS upstreamStatus, update_time AS updateTime
|
||||
FROM canteen_hotel
|
||||
WHERE del_flag = '0'
|
||||
AND (upstream_status IS NULL OR upstream_status <> 'OFFLINE')
|
||||
AND hotel_name LIKE CONCAT('%', #{keyword}, '%')
|
||||
<if test="adFrom != null">
|
||||
AND district_adcode >= #{adFrom} AND district_adcode < #{adTo}
|
||||
</if>
|
||||
<if test="starLevels != null and starLevels.size() > 0">
|
||||
AND star_level IN
|
||||
<foreach collection="starLevels" item="s" open="(" separator="," close=")">#{s}</foreach>
|
||||
</if>
|
||||
LIMIT #{limit}
|
||||
</script>
|
||||
""")
|
||||
List<SmHotelEntity> selectAppByNameContains(@Param("keyword") String keyword, @Param("adFrom") Long adFrom,
|
||||
@Param("adTo") Long adTo, @Param("starLevels") Collection<Integer> starLevels,
|
||||
@Param("limit") int limit);
|
||||
}
|
||||
+5
-2
@@ -50,8 +50,11 @@ public interface SmHotelCallbackTaskService {
|
||||
/** 清空全部回调任务,并清空 Redis 消费队列 */
|
||||
R<Integer> clearAllTasks();
|
||||
|
||||
/** 消费一条队列任务(Worker 调用);返回 true=本次成功/跳过,false=失败或待再试 */
|
||||
boolean processQueuedTask(Long taskId);
|
||||
/**
|
||||
* 消费一条队列任务(Worker 调用)。
|
||||
* @return 1 已处理成功,0 跳过(非 PENDING / 不存在),-1 失败待重试
|
||||
*/
|
||||
int processQueuedTask(Long taskId);
|
||||
|
||||
/** 启动/人工:把全部 PENDING + 超时 RUNNING 重新入队 */
|
||||
int requeueStalePending();
|
||||
|
||||
+48
-15
@@ -63,6 +63,9 @@ public class SmHotelCallbackTaskServiceImpl implements SmHotelCallbackTaskServic
|
||||
|
||||
private final StringRedisTemplate redisTemplate;
|
||||
|
||||
/** Redis 残留 SUCCESS 副本跳过计数(避免启动刷屏) */
|
||||
private long skipNonPendingCount;
|
||||
|
||||
/** 与 Worker 一致:读渠道库表覆盖后的运行时,不要只读 YAML 默认值 */
|
||||
private ShangyouHotelProperties props() {
|
||||
return channelConfigService.resolveShangyouRuntime();
|
||||
@@ -311,6 +314,7 @@ public class SmHotelCallbackTaskServiceImpl implements SmHotelCallbackTaskServic
|
||||
Long qHigh = redisTemplate.opsForList().size(ShangyouHotelApi.REDIS_CALLBACK_QUEUE_HIGH);
|
||||
redisTemplate.delete(ShangyouHotelApi.REDIS_CALLBACK_QUEUE);
|
||||
redisTemplate.delete(ShangyouHotelApi.REDIS_CALLBACK_QUEUE_HIGH);
|
||||
redisTemplate.delete(ShangyouHotelApi.REDIS_CALLBACK_QUEUED);
|
||||
log.warn("|回调任务|清空全部|deleted={}|queueCleared≈{}/high≈{}|", n, qLen, qHigh);
|
||||
return R.ok(n, "已清空 " + n + " 条任务,并清理队列"
|
||||
+ (cnt != null && cnt > n ? "(处理中任务未删)" : ""));
|
||||
@@ -339,14 +343,14 @@ public class SmHotelCallbackTaskServiceImpl implements SmHotelCallbackTaskServic
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean processQueuedTask(Long taskId) {
|
||||
public int processQueuedTask(Long taskId) {
|
||||
if (taskId == null) {
|
||||
return true;
|
||||
return 0;
|
||||
}
|
||||
ShangyouHotelProperties p = props();
|
||||
Long tenantId = p.getCallbackTenantId() == null ? 1L : p.getCallbackTenantId();
|
||||
Boolean ok = TenantBroker.applyAs(tenantId, tid -> doProcess(taskId));
|
||||
return Boolean.TRUE.equals(ok);
|
||||
Integer r = TenantBroker.applyAs(tenantId, tid -> doProcess(taskId));
|
||||
return r == null ? -1 : r;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -433,16 +437,25 @@ public class SmHotelCallbackTaskServiceImpl implements SmHotelCallbackTaskServic
|
||||
return n;
|
||||
}
|
||||
|
||||
/** @return true 成功或跳过;false 失败/待重试 */
|
||||
private boolean doProcess(Long taskId) {
|
||||
/** @return 1 成功,0 跳过,-1 失败 */
|
||||
private int doProcess(Long taskId) {
|
||||
SmHotelCallbackTaskEntity task = taskMapper.selectById(taskId);
|
||||
// 已从 List 弹出,去掉 queued 标记,失败重试才能再次入队
|
||||
unmarkQueued(taskId);
|
||||
if (task == null) {
|
||||
log.warn("|回调队列|任务不存在|taskId={}|", taskId);
|
||||
return true;
|
||||
return 0;
|
||||
}
|
||||
if (!STATUS_PENDING.equals(task.getStatus())) {
|
||||
log.info("|回调队列|跳过非 PENDING|taskId={}|status={}|", taskId, task.getStatus());
|
||||
return true;
|
||||
skipNonPendingCount++;
|
||||
if (skipNonPendingCount == 1 || skipNonPendingCount % 200 == 0) {
|
||||
log.info("|回调队列|跳过非 PENDING(Redis 残留已完成副本)|taskId={}|status={}|skipped={}|",
|
||||
taskId, task.getStatus(), skipNonPendingCount);
|
||||
}
|
||||
else {
|
||||
log.debug("|回调队列|跳过非 PENDING|taskId={}|status={}|", taskId, task.getStatus());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
int attempts = task.getAttemptCount() == null ? 0 : task.getAttemptCount();
|
||||
int max = Math.max(1, props().getCallbackMaxAttempts());
|
||||
@@ -460,7 +473,7 @@ public class SmHotelCallbackTaskServiceImpl implements SmHotelCallbackTaskServic
|
||||
: JSONUtil.parseObj(task.getReqJson());
|
||||
Map<String, Object> result = orderService.handleOrderStatusNotify(body);
|
||||
finish(task, STATUS_SUCCESS, "订单状态更新: " + JSONUtil.toJsonStr(result), t0);
|
||||
return true;
|
||||
return 1;
|
||||
}
|
||||
if (task.getHotelId() == null && StrUtil.isNotBlank(task.getHotelCode())) {
|
||||
SmHotelEntity hotel = resolveWritableHotelByCode(task.getHotelCode(), SmHotelDataSource.SHANGYOU);
|
||||
@@ -472,7 +485,7 @@ public class SmHotelCallbackTaskServiceImpl implements SmHotelCallbackTaskServic
|
||||
}
|
||||
if (task.getHotelId() == null) {
|
||||
finish(task, STATUS_SKIPPED, "无商游可写酒店(hotelCode=" + task.getHotelCode() + "),本地或其它渠道不会覆盖", t0);
|
||||
return true;
|
||||
return 1;
|
||||
}
|
||||
// 消费前把同酒店其余待处理房价/房量并进本条,积压的重复通知不再各拉一次
|
||||
int folded = foldPendingSiblings(task);
|
||||
@@ -487,7 +500,7 @@ public class SmHotelCallbackTaskServiceImpl implements SmHotelCallbackTaskServic
|
||||
SmHotelDataSource.denyWriteMessage(bound == null ? null : bound.getSheetName(),
|
||||
SmHotelDataSource.SHANGYOU),
|
||||
t0);
|
||||
return true;
|
||||
return 1;
|
||||
}
|
||||
// 回调只拉通知日期区间,避免每次全量 90 天把单线程队列堵死
|
||||
R<Map<String, Object>> pull = pullForCallback(task);
|
||||
@@ -495,11 +508,11 @@ public class SmHotelCallbackTaskServiceImpl implements SmHotelCallbackTaskServic
|
||||
Object data = pull.getData();
|
||||
finish(task, STATUS_SUCCESS,
|
||||
"拉取成功" + (data == null ? "" : ": " + StrUtil.maxLength(JSONUtil.toJsonStr(data), 800)), t0);
|
||||
return true;
|
||||
return 1;
|
||||
}
|
||||
String msg = pull == null ? "拉取返回空" : StrUtil.blankToDefault(pull.getMsg(), "拉取失败");
|
||||
handleFailOrRetry(task, msg, t0, max);
|
||||
return false;
|
||||
return -1;
|
||||
}
|
||||
catch (Exception e) {
|
||||
String msg = StrUtil.maxLength(e.getMessage(), 800);
|
||||
@@ -511,7 +524,7 @@ public class SmHotelCallbackTaskServiceImpl implements SmHotelCallbackTaskServic
|
||||
log.error("|回调队列|处理异常|taskId={}|", taskId, e);
|
||||
}
|
||||
handleFailOrRetry(task, msg, t0, max);
|
||||
return false;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -616,6 +629,7 @@ public class SmHotelCallbackTaskServiceImpl implements SmHotelCallbackTaskServic
|
||||
task.setFinishTime(LocalDateTime.now());
|
||||
task.setUpdateTime(LocalDateTime.now());
|
||||
taskMapper.updateById(task);
|
||||
unmarkQueued(task.getId());
|
||||
log.info("|回调队列|完成|taskId={}|status={}|costMs={}|msg={}|", task.getId(), status, task.getCostMs(),
|
||||
task.getResultMsg());
|
||||
}
|
||||
@@ -765,7 +779,14 @@ public class SmHotelCallbackTaskServiceImpl implements SmHotelCallbackTaskServic
|
||||
|
||||
/** 订单状态回调最高优先:进高优队列,Worker 始终先于房价/房量消费 */
|
||||
private void enqueueTaskId(Long taskId, String notifyType) {
|
||||
if (taskId == null) {
|
||||
return;
|
||||
}
|
||||
String id = String.valueOf(taskId);
|
||||
Long added = redisTemplate.opsForSet().add(ShangyouHotelApi.REDIS_CALLBACK_QUEUED, id);
|
||||
if (added != null && added == 0L) {
|
||||
return;
|
||||
}
|
||||
if (isHighPriority(notifyType)) {
|
||||
redisTemplate.opsForList().leftPush(ShangyouHotelApi.REDIS_CALLBACK_QUEUE_HIGH, id);
|
||||
}
|
||||
@@ -774,6 +795,18 @@ public class SmHotelCallbackTaskServiceImpl implements SmHotelCallbackTaskServic
|
||||
}
|
||||
}
|
||||
|
||||
private void unmarkQueued(Long taskId) {
|
||||
if (taskId == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
redisTemplate.opsForSet().remove(ShangyouHotelApi.REDIS_CALLBACK_QUEUED, String.valueOf(taskId));
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.debug("|回调队列|清除 queued 标记失败|taskId={}|{}", taskId, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isHighPriority(String notifyType) {
|
||||
return "ORDER_STATUS".equalsIgnoreCase(notifyType);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user