酒店优化

This commit is contained in:
zjf committed 2026-10-20 12:47:16 +08:00
1 parent ce0e9e3868
commit a74dec5e7a
4 files changed
+251 -136

No files matched your search

@@ -2,6 +2,7 @@ package com.bsd.pjcloud.supermarket.controller;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -16,16 +17,20 @@ 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.SmHotelProductMapper;
import com.bsd.pjcloud.supermarket.mapper.SmHotelRoomMapper;
import com.bsd.pjcloud.supermarket.mapper.SmHotelUserSearchPrefMapper;
import com.bsd.pjcloud.supermarket.service.SmHotelMidService;
import com.bsd.pjcloud.supermarket.service.SmHotelOrderService;
import com.bsd.pjcloud.supermarket.service.SmHotelService;
import com.bsd.pjcloud.supermarket.util.HotelAreaAdcodeUtil;
import com.bsd.pjcloud.supermarket.vo.SmHotelDetailVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;
@@ -42,11 +47,13 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 移动端 TOC 酒店预订(列表/详情/下单/我的订单)。
*/
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/app/hotel")
@@ -62,9 +69,23 @@ public class SmHotelAppController {
private static final DateTimeFormatter DAY = DateTimeFormatter.ISO_LOCAL_DATE;
/** 候选酒店 id 用 IN 时的上限;过大则回退 EXISTS,避免超长 SQL */
/** 候选酒店 id 用 IN 时的上限;过大则回退 IN(子查询),避免超长 SQL / 相关 EXISTS */
private static final int MAX_IN_HOTEL_IDS = 8000;
/** 筛选项缓存(租户前缀由 Redis 基础设施自动处理) */
private static final String FILTER_META_CACHE_KEY = "sm:hotel:app:filter-meta";
private static final long FILTER_META_CACHE_MINUTES = 10L;
/** 可售候选酒店 id 短缓存 */
private static final String CANDIDATE_CACHE_PREFIX = "sm:hotel:app:cand:";
private static final long CANDIDATE_CACHE_MINUTES = 3L;
private static final String CANDIDATE_CACHE_EMPTY = "EMPTY";
private static final String CANDIDATE_CACHE_OVERFLOW = "OVERFLOW";
private final SmHotelService hotelService;
private final SmHotelMidService midService;
@@ -77,34 +98,82 @@ public class SmHotelAppController {
private final SmHotelRoomMapper roomMapper;
private final SmHotelProductMapper productMapper;
private final StringRedisTemplate stringRedisTemplate;
@Operation(summary = "列表筛选项(区域/星级/设施)")
@GetMapping("/filter-meta")
public R<Map<String, Object>> filterMeta() {
var online = Wrappers.<SmHotelEntity>lambdaQuery()
try {
String cached = stringRedisTemplate.opsForValue().get(FILTER_META_CACHE_KEY);
if (StrUtil.isNotBlank(cached)) {
@SuppressWarnings("unchecked")
Map<String, Object> hit = JSONUtil.toBean(cached, Map.class);
if (hit != null && !hit.isEmpty()) {
return R.ok(hit);
}
}
}
catch (Exception e) {
log.warn("|酒店App|filter-meta缓存读取失败|{}", e.getMessage());
}
Map<String, Object> out = buildFilterMeta();
try {
stringRedisTemplate.opsForValue()
.set(FILTER_META_CACHE_KEY, JSONUtil.toJsonStr(out), FILTER_META_CACHE_MINUTES, TimeUnit.MINUTES);
}
catch (Exception e) {
log.warn("|酒店App|filter-meta缓存写入失败|{}", e.getMessage());
}
return R.ok(out);
}
/** 上架酒店公共条件(未下架) */
private static LambdaQueryWrapper<SmHotelEntity> onlineHotelWrapper() {
return Wrappers.<SmHotelEntity>lambdaQuery()
.and(w -> w.isNull(SmHotelEntity::getUpstreamStatus)
.or()
.ne(SmHotelEntity::getUpstreamStatus, "OFFLINE"))
.select(SmHotelEntity::getId, SmHotelEntity::getDistrict, SmHotelEntity::getBizArea,
SmHotelEntity::getFacilityTags);
List<SmHotelEntity> rows = hotelService.list(online);
.ne(SmHotelEntity::getUpstreamStatus, "OFFLINE"));
}
/**
* 筛选项:按列 GROUP BY,避免全表加载酒店实体;districts 仅兜底(App 优先走行政区划接口)。
*/
private Map<String, Object> buildFilterMeta() {
Set<String> districts = new LinkedHashSet<>();
Set<String> bizAreas = new LinkedHashSet<>();
Set<String> facilities = new LinkedHashSet<>(DEFAULT_FACILITY_OPTIONS);
for (SmHotelEntity h : rows) {
if (h == null) {
continue;
}
if (StrUtil.isNotBlank(h.getDistrict())) {
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());
}
if (StrUtil.isNotBlank(h.getBizArea())) {
}
Set<String> bizAreas = new LinkedHashSet<>();
for (SmHotelEntity h : hotelService.list(onlineHotelWrapper()
.select(SmHotelEntity::getBizArea)
.isNotNull(SmHotelEntity::getBizArea)
.ne(SmHotelEntity::getBizArea, "")
.groupBy(SmHotelEntity::getBizArea))) {
if (h != null && StrUtil.isNotBlank(h.getBizArea())) {
bizAreas.add(h.getBizArea().trim());
}
if (StrUtil.isNotBlank(h.getFacilityTags())) {
for (String t : h.getFacilityTags().split("[,,]")) {
if (StrUtil.isNotBlank(t)) {
facilities.add(t.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());
}
}
}
@@ -117,7 +186,6 @@ public class SmHotelAppController {
});
starLevels.sort((a, b) -> Integer.compare((Integer) b.get("value"), (Integer) a.get("value")));
Map<String, Object> out = new LinkedHashMap<>();
// districts 仅作兜底;移动端优先走 /admin/sysArea/app/children(行政区划)
out.put("districts", new ArrayList<>(districts));
out.put("bizAreas", new ArrayList<>(bizAreas));
out.put("starLevels", starLevels);
@@ -129,7 +197,7 @@ public class SmHotelAppController {
Map.of("label", "300-500", "min", 300, "max", 500),
Map.of("label", "500以上", "min", 500, "max", 999999)));
out.put("areaSource", "sysArea");
return R.ok(out);
return out;
}
@Operation(summary = "上架酒店分页(关键字/区域/星级/评分/价格/设施/人数/入住区间)")
@@ -170,10 +238,14 @@ public class SmHotelAppController {
.or()
.like(SmHotelEntity::getDistrict, city.trim())
.or()
.like(SmHotelEntity::getHotelName, city.trim()))
.eq(districtAdcode == null && StrUtil.isNotBlank(district), SmHotelEntity::getDistrict, StrUtil.trim(district));
// district 兼作区域名称(选省/市未选下级时传「浙江省」等),与 adcode 前缀一起匹配
applyAreaAdcode(wrapper, districtAdcode, district);
.like(SmHotelEntity::getHotelName, city.trim()));
// 有 adcode:数值区间走索引 + 名称兜底;仅名称:保持精确匹配(与历史 App 行为一致)
if (districtAdcode != null) {
HotelAreaAdcodeUtil.applyHotelListFilter(wrapper, districtAdcode, district);
}
else if (StrUtil.isNotBlank(district)) {
wrapper.eq(SmHotelEntity::getDistrict, StrUtil.trim(district));
}
wrapper.like(StrUtil.isNotBlank(bizArea), SmHotelEntity::getBizArea, StrUtil.trim(bizArea))
.in(!starList.isEmpty(), SmHotelEntity::getStarLevel, starList)
.ge(minScore != null, SmHotelEntity::getScore, minScore)
@@ -199,7 +271,7 @@ public class SmHotelAppController {
wrapper.in(SmHotelEntity::getId, candidateIds);
}
else {
// 候选过多:回退轻量 EXISTS(不再 join product)
// 候选过多:用 IN(子查询) 替代相关 EXISTS,语义不变、更易走 stay_date 索引
applyBookableHotelFilter(wrapper, stayFrom, stayToExclusive);
applyGuestCapacityFilter(wrapper, needCap);
}
@@ -515,61 +587,6 @@ public class SmHotelAppController {
return orderService.cancelByAdmin(id, reason);
}
/**
* 按行政区划过滤:
* - 省/市 adcode 用前缀覆盖全部下级(选省不选市 = 该省全部)
* - 同时兼容仅填了区域名称、未落 district_adcode 的酒店(按名称模糊)
*/
private void applyAreaAdcode(LambdaQueryWrapper<SmHotelEntity> wrapper, Long districtAdcode, String areaName) {
if (districtAdcode == null) {
return;
}
String prefix = areaAdcodePrefix(districtAdcode);
String name = StrUtil.trim(areaName);
String shortName = shortenAreaName(name);
wrapper.and(w -> {
w.apply("district_adcode is not null and CAST(district_adcode AS CHAR) LIKE {0}", prefix + "%");
if (StrUtil.isNotBlank(name)) {
w.or().like(SmHotelEntity::getDistrict, name)
.or()
.like(SmHotelEntity::getAddress, name);
}
if (StrUtil.isNotBlank(shortName) && !shortName.equals(name)) {
w.or().like(SmHotelEntity::getDistrict, shortName)
.or()
.like(SmHotelEntity::getAddress, shortName);
}
});
}
/** 330000→33,330100→3301,330102→330102;保证选省能命中区县码 */
static String areaAdcodePrefix(long adcode) {
String s = String.valueOf(adcode);
// 街道等长码:用所属区县 6 位前缀
if (s.length() > 6) {
return s.substring(0, 6);
}
if (s.length() == 6 && s.endsWith("0000")) {
return s.substring(0, 2);
}
if (s.length() == 6 && s.endsWith("00")) {
return s.substring(0, 4);
}
return s;
}
/** 「浙江省」→「浙江」,便于名称兜底 */
static String shortenAreaName(String name) {
if (StrUtil.isBlank(name)) {
return "";
}
String n = name.trim();
n = n.replaceAll("(壮族|回族|维吾尔|藏族|彝族|朝鲜族)?自治区$", "");
n = n.replaceAll("特别行政区$", "");
n = n.replaceAll("(省|市|区|县|旗|盟|州)$", "");
return n.trim();
}
private Map<String, Object> toListRow(SmHotelEntity h, BigDecimal minSellPrice) {
Map<String, Object> row = new LinkedHashMap<>();
if (h == null) {
@@ -598,18 +615,15 @@ public class SmHotelAppController {
if (hotelIds == null || hotelIds.isEmpty()) {
return map;
}
List<SmHotelProductEntity> products = Db.lambdaQuery(SmHotelProductEntity.class)
.in(SmHotelProductEntity::getHotelId, hotelIds)
.select(SmHotelProductEntity::getHotelId, SmHotelProductEntity::getSellPrice)
.list();
for (SmHotelProductEntity p : products) {
if (p.getHotelId() == null || p.getSellPrice() == null) {
List<Map<String, Object>> rows = productMapper.selectMinSellPriceByHotelIds(hotelIds);
if (rows == null) {
return map;
}
for (Map<String, Object> row : rows) {
if (row == null || row.get("hotelId") == null || row.get("minSell") == null) {
continue;
}
BigDecimal old = map.get(p.getHotelId());
if (old == null || p.getSellPrice().compareTo(old) < 0) {
map.put(p.getHotelId(), p.getSellPrice());
}
map.put(Long.valueOf(String.valueOf(row.get("hotelId"))), new BigDecimal(String.valueOf(row.get("minSell"))));
}
return map;
}
@@ -618,7 +632,7 @@ public class SmHotelAppController {
* 解析列表候选酒店:
* 1) 入住区间内有日历;
* 2) 可选:房型可住人数达标。
* 返回 null 表示候选过多,调用方回退 EXISTS。
* 返回 null 表示候选过多,调用方回退 IN(子查询)。
*/
private List<Long> resolveAppListCandidateHotelIds(LocalDate stayFrom, LocalDate stayToExclusive, int needCapPerRoom) {
LocalDate from = stayFrom != null ? stayFrom : LocalDate.now();
@@ -627,29 +641,63 @@ public class SmHotelAppController {
// 未传离店:只扫未来 30 天有日历的酒店,避免无上界扫全表
toEx = from.plusDays(30);
}
String cacheKey = CANDIDATE_CACHE_PREFIX + from + ":" + toEx + ":" + needCapPerRoom;
try {
String cached = stringRedisTemplate.opsForValue().get(cacheKey);
if (CANDIDATE_CACHE_EMPTY.equals(cached)) {
return List.of();
}
if (CANDIDATE_CACHE_OVERFLOW.equals(cached)) {
return null;
}
if (StrUtil.isNotBlank(cached)) {
return Arrays.stream(cached.split(","))
.map(String::trim)
.filter(StrUtil::isNotBlank)
.map(Long::valueOf)
.toList();
}
}
catch (Exception e) {
log.warn("|酒店App|候选缓存读取失败|{}", e.getMessage());
}
List<Long> bookable = dailyMapper.selectDistinctHotelIdsByStayRange(from, toEx);
if (bookable == null || bookable.isEmpty()) {
cacheCandidateIds(cacheKey, CANDIDATE_CACHE_EMPTY);
return List.of();
}
List<Long> ids = bookable.stream().filter(Objects::nonNull).distinct().toList();
if (needCapPerRoom > 1) {
List<Long> capHotels = roomMapper.selectDistinctHotelIdsByMinCapacity(needCapPerRoom);
if (capHotels == null || capHotels.isEmpty()) {
cacheCandidateIds(cacheKey, CANDIDATE_CACHE_EMPTY);
return List.of();
}
Set<Long> capSet = new java.util.HashSet<>(capHotels);
ids = ids.stream().filter(capSet::contains).toList();
if (ids.isEmpty()) {
cacheCandidateIds(cacheKey, CANDIDATE_CACHE_EMPTY);
return List.of();
}
}
if (ids.size() > MAX_IN_HOTEL_IDS) {
cacheCandidateIds(cacheKey, CANDIDATE_CACHE_OVERFLOW);
return null;
}
cacheCandidateIds(cacheKey, ids.stream().map(String::valueOf).collect(Collectors.joining(",")));
return ids;
}
/** 轻量 EXISTS:仅日历表,不再 join product */
private void cacheCandidateIds(String cacheKey, String value) {
try {
stringRedisTemplate.opsForValue().set(cacheKey, value, CANDIDATE_CACHE_MINUTES, TimeUnit.MINUTES);
}
catch (Exception e) {
log.warn("|酒店App|候选缓存写入失败|{}", e.getMessage());
}
}
/** 可售:IN(日历 DISTINCT 子查询),替代相关 EXISTS */
private void applyBookableHotelFilter(LambdaQueryWrapper<SmHotelEntity> wrapper, LocalDate stayFrom,
LocalDate stayToExclusive) {
LocalDate from = stayFrom != null ? stayFrom : LocalDate.now();
@@ -658,10 +706,9 @@ public class SmHotelAppController {
toEx = from.plusDays(30);
}
wrapper.apply("""
EXISTS (
SELECT 1 FROM canteen_hotel_daily d
WHERE d.hotel_id = canteen_hotel.id
AND d.del_flag = '0'
id IN (
SELECT DISTINCT d.hotel_id FROM canteen_hotel_daily d
WHERE d.del_flag = '0'
AND d.stay_date >= {0}
AND d.stay_date < {1}
)
@@ -677,10 +724,9 @@ public class SmHotelAppController {
return;
}
wrapper.apply("""
EXISTS (
SELECT 1 FROM canteen_hotel_room r
WHERE r.hotel_id = canteen_hotel.id
AND r.del_flag = '0'
id IN (
SELECT DISTINCT r.hotel_id FROM canteen_hotel_room r
WHERE r.del_flag = '0'
AND (r.capacity IS NULL OR r.capacity >= {0})
)
""", needCapPerRoom);
@@ -754,34 +800,10 @@ public class SmHotelAppController {
return m;
}
/** 按产品最低卖价落在区间内的酒店 id(起价筛选) */
/** 按产品最低卖价落在区间内的酒店 id(起价筛选,SQL 聚合) */
private List<Long> loadHotelIdsByMinPriceRange(BigDecimal minPrice, BigDecimal maxPrice) {
List<SmHotelProductEntity> products = Db.lambdaQuery(SmHotelProductEntity.class)
.isNotNull(SmHotelProductEntity::getSellPrice)
.select(SmHotelProductEntity::getHotelId, SmHotelProductEntity::getSellPrice)
.list();
Map<Long, BigDecimal> minMap = new HashMap<>();
for (SmHotelProductEntity p : products) {
if (p.getHotelId() == null || p.getSellPrice() == null) {
continue;
}
BigDecimal old = minMap.get(p.getHotelId());
if (old == null || p.getSellPrice().compareTo(old) < 0) {
minMap.put(p.getHotelId(), p.getSellPrice());
}
}
List<Long> ids = new ArrayList<>();
for (Map.Entry<Long, BigDecimal> e : minMap.entrySet()) {
BigDecimal v = e.getValue();
if (minPrice != null && v.compareTo(minPrice) < 0) {
continue;
}
if (maxPrice != null && v.compareTo(maxPrice) > 0) {
continue;
}
ids.add(e.getKey());
}
return ids;
List<Long> ids = productMapper.selectHotelIdsByMinSellPriceRange(minPrice, maxPrice);
return ids == null ? List.of() : ids.stream().filter(Objects::nonNull).distinct().toList();
}
private static List<Integer> parseIntCsv(String csv) {
@@ -40,4 +40,36 @@ public interface SmHotelProductMapper extends PjcloudBaseMapper<SmHotelProductEn
</script>
""")
List<Map<String, Object>> countGroupByHotelIds(@Param("ids") Collection<Long> ids);
/**
* 移动端列表起价筛选:按酒店最低卖价落在区间内。
*/
@Select("""
<script>
SELECT hotel_id FROM canteen_hotel_product
WHERE del_flag = '0' AND sell_price IS NOT NULL
GROUP BY hotel_id
HAVING 1 = 1
<if test="minPrice != null">
AND MIN(sell_price) &gt;= #{minPrice}
</if>
<if test="maxPrice != null">
AND MIN(sell_price) &lt;= #{maxPrice}
</if>
</script>
""")
List<Long> selectHotelIdsByMinSellPriceRange(@Param("minPrice") BigDecimal minPrice,
@Param("maxPrice") BigDecimal maxPrice);
/** 本页酒店批量取最低卖价 */
@Select("""
<script>
SELECT hotel_id AS hotelId, MIN(sell_price) AS minSell
FROM canteen_hotel_product
WHERE del_flag = '0' AND sell_price IS NOT NULL AND hotel_id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">#{id}</foreach>
GROUP BY hotel_id
</script>
""")
List<Map<String, Object>> selectMinSellPriceByHotelIds(@Param("ids") Collection<Long> ids);
}
@@ -5,8 +5,8 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.bsd.pjcloud.supermarket.entity.SmHotelEntity;
/**
* 行政区划 adcode 前缀匹配(与 App 列表筛选一致):
* 选省/市可覆盖全部下级区县。
* 行政区划 adcode 匹配(App / PC 列表一致):
* 选省/市覆盖全部下级区县;优先数值区间走索引,名称仅作无码数据兜底。
*/
public final class HotelAreaAdcodeUtil {
@@ -15,7 +15,7 @@ public final class HotelAreaAdcodeUtil {
/**
* 酒店列表按位置区域过滤:
* - 省/市 adcode 用前缀覆盖全部下级;
* - 省/市 adcode 用数值区间覆盖全部下级(走 district_adcode 索引);
* - 同时兼容仅填了区域名称、未落 district_adcode 的酒店(按名称模糊)。
*/
public static void applyHotelListFilter(LambdaQueryWrapper<SmHotelEntity> wrapper, Long districtAdcode,
@@ -34,11 +34,13 @@ public final class HotelAreaAdcodeUtil {
});
return;
}
String prefix = areaAdcodePrefix(districtAdcode);
long[] range = areaAdcodeRange(districtAdcode);
String name = StrUtil.trim(areaName);
String shortName = shortenAreaName(name);
wrapper.and(w -> {
w.apply("district_adcode is not null and CAST(district_adcode AS CHAR) LIKE {0}", prefix + "%");
// 数值半开区间,等价于原 CAST LIKE 前缀,可走 idx_hotel_district_adcode
w.apply("district_adcode is not null and district_adcode >= {0} and district_adcode < {1}", range[0],
range[1]);
if (StrUtil.isNotBlank(name)) {
w.or().like(SmHotelEntity::getDistrict, name).or().like(SmHotelEntity::getAddress, name);
}
@@ -48,7 +50,40 @@ public final class HotelAreaAdcodeUtil {
});
}
/** 330000→33,330100→3301,330102→330102 */
/**
* adcode → 半开区间 [min, maxExclusive)。
* <ul>
* <li>省 330000 → [330000, 340000)</li>
* <li>市 330100 → [330100, 330200)</li>
* <li>区 330102 / 街道长码 → [330102, 330103)</li>
* </ul>
*/
public static long[] areaAdcodeRange(long adcode) {
String s = String.valueOf(adcode);
if (s.length() > 6) {
long district = Long.parseLong(s.substring(0, 6));
return new long[] { district, district + 1 };
}
if (s.length() < 6) {
StringBuilder sb = new StringBuilder(s);
while (sb.length() < 6) {
sb.append('0');
}
s = sb.toString();
}
if (s.endsWith("0000")) {
long base = Long.parseLong(s.substring(0, 2)) * 10000L;
return new long[] { base, base + 10000L };
}
if (s.endsWith("00")) {
long base = Long.parseLong(s.substring(0, 4)) * 100L;
return new long[] { base, base + 100L };
}
long district = Long.parseLong(s);
return new long[] { district, district + 1 };
}
/** 330000→33,330100→3301,330102→330102(兼容旧调用) */
public static String areaAdcodePrefix(long adcode) {
String s = String.valueOf(adcode);
if (s.length() > 6) {
@@ -76,8 +111,8 @@ public final class HotelAreaAdcodeUtil {
return true;
}
if (hotelAdcode != null) {
String prefix = areaAdcodePrefix(scopeAdcode);
return String.valueOf(hotelAdcode).startsWith(prefix);
long[] range = areaAdcodeRange(scopeAdcode);
return hotelAdcode >= range[0] && hotelAdcode < range[1];
}
// 酒店未落 adcode:按区域名兜底
String name = StrUtil.trim(scopeName);
@@ -0,0 +1,26 @@
package com.bsd.pjcloud.supermarket.util;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class HotelAreaAdcodeUtilTest {
@Test
void areaAdcodeRange_provinceCityDistrict() {
assertArrayEquals(new long[] { 330000L, 340000L }, HotelAreaAdcodeUtil.areaAdcodeRange(330000L));
assertArrayEquals(new long[] { 330100L, 330200L }, HotelAreaAdcodeUtil.areaAdcodeRange(330100L));
assertArrayEquals(new long[] { 330102L, 330103L }, HotelAreaAdcodeUtil.areaAdcodeRange(330102L));
assertArrayEquals(new long[] { 330102L, 330103L }, HotelAreaAdcodeUtil.areaAdcodeRange(330102001L));
}
@Test
void matchRegion_numericEquivalentToPrefix() {
assertTrue(HotelAreaAdcodeUtil.matchRegion(330000L, "浙江", 330102L, "南湖区"));
assertTrue(HotelAreaAdcodeUtil.matchRegion(330100L, "嘉兴", 330102L, "南湖区"));
assertTrue(HotelAreaAdcodeUtil.matchRegion(330102L, "南湖区", 330102L, "南湖区"));
assertFalse(HotelAreaAdcodeUtil.matchRegion(330100L, "嘉兴", 330200L, "宁波"));
}
}