酒店
This commit is contained in:
1 parent
8dcaae6e3d
commit
730ae9b447
2 files changed
+137
-26
No files matched your search
+79
-26
@@ -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;
|
||||
@@ -89,7 +90,7 @@ public class SmHotelAppController {
|
||||
private static final String CANDIDATE_CACHE_OVERFLOW = "OVERFLOW";
|
||||
|
||||
/** 列表页结果短缓存(同条件重复进入/下拉) */
|
||||
private static final String PAGE_CACHE_PREFIX = "sm:hotel:app:page:v12:";
|
||||
private static final String PAGE_CACHE_PREFIX = "sm:hotel:app:page:v13:";
|
||||
|
||||
private static final long PAGE_CACHE_SECONDS = 45L;
|
||||
|
||||
@@ -100,8 +101,13 @@ public class SmHotelAppController {
|
||||
/** 城市/关键字等缩小后,在该规模内内存排序(超出则 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;
|
||||
@@ -228,31 +234,28 @@ public class SmHotelAppController {
|
||||
return R.ok(cached);
|
||||
}
|
||||
|
||||
boolean hasArea = districtAdcode != null || StrUtil.isNotBlank(city) || StrUtil.isNotBlank(district);
|
||||
var wrapper = applyListColumns(baseOnlineListWrapper(keyword, city, district, districtAdcode, bizArea,
|
||||
starList, minScore, facilityList, true));
|
||||
if (hasArea) {
|
||||
applyGuestCapacityFilter(wrapper, needCap);
|
||||
applyMinProductPriceFilter(wrapper, minPrice, maxPrice);
|
||||
Page<SmHotelEntity> raw;
|
||||
if (StrUtil.isNotBlank(keyword)) {
|
||||
// 店名搜索专用路径:禁止「LIKE %kw% + ORDER BY」全表 filesort(搜「小如」会超时变空)
|
||||
raw = pageByHotelNameKeyword(page, keyword.trim(), city, district, districtAdcode, starList);
|
||||
}
|
||||
wrapper.last("ORDER BY star_level DESC, id DESC");
|
||||
Page<SmHotelEntity> raw = pageWithoutCount(page, wrapper);
|
||||
if (StrUtil.isNotBlank(keyword) && (raw.getRecords() == null || raw.getRecords().isEmpty())) {
|
||||
var infix = applyListColumns(baseOnlineListWrapper(keyword, city, district, districtAdcode, bizArea,
|
||||
starList, minScore, facilityList, false));
|
||||
else {
|
||||
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(infix, needCap);
|
||||
applyMinProductPriceFilter(infix, minPrice, maxPrice);
|
||||
applyGuestCapacityFilter(wrapper, needCap);
|
||||
applyMinProductPriceFilter(wrapper, minPrice, maxPrice);
|
||||
}
|
||||
infix.last("ORDER BY star_level DESC, id DESC");
|
||||
raw = pageWithoutCount(page, infix);
|
||||
}
|
||||
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, keyword, bizArea, starList, minScore, facilityList, needCap,
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
List<Long> ids = raw.getRecords().stream().map(SmHotelEntity::getId).filter(Objects::nonNull).toList();
|
||||
@@ -420,6 +423,55 @@ 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,
|
||||
@@ -577,6 +629,7 @@ public class SmHotelAppController {
|
||||
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)
|
||||
@@ -589,11 +642,11 @@ public class SmHotelAppController {
|
||||
.eq(SmHotelEntity::getDistrict, name + "区")
|
||||
.or()
|
||||
.eq(SmHotelEntity::getDistrict, name + "县");
|
||||
if (StrUtil.isNotBlank(shortName)) {
|
||||
w.or().likeRight(SmHotelEntity::getDistrict, shortName);
|
||||
if (StrUtil.isNotBlank(shortName) && !shortName.equals(name)) {
|
||||
w.or().eq(SmHotelEntity::getDistrict, shortName);
|
||||
}
|
||||
})
|
||||
.last("LIMIT 50"));
|
||||
.last("LIMIT 20"));
|
||||
Long adcode = pickPlaceAdcode(rows, promoteToCity);
|
||||
try {
|
||||
stringRedisTemplate.opsForValue()
|
||||
|
||||
+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);
|
||||
}
|
||||
Reference in new issue
Block a user