diff --git a/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/entity/SmHotelOrderEntity.java b/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/entity/SmHotelOrderEntity.java index acdb8ed4..1a8c92f6 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/entity/SmHotelOrderEntity.java +++ b/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/entity/SmHotelOrderEntity.java @@ -163,4 +163,13 @@ public class SmHotelOrderEntity extends Model { @TableField(exist = false) @Schema(description = "酒店数据来源") private String hotelDataSource; + + /** 列表展示:酒店归属部门(对应商户,结算用;非表字段,来自 canteen_hotel.dept_id) */ + @TableField(exist = false) + @Schema(description = "酒店归属部门ID") + private Long deptId; + + @TableField(exist = false) + @Schema(description = "酒店归属部门名称") + private String deptName; } diff --git a/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/vo/SmHotelOrderDetailVo.java b/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/vo/SmHotelOrderDetailVo.java index 39db5422..19858040 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/vo/SmHotelOrderDetailVo.java +++ b/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/vo/SmHotelOrderDetailVo.java @@ -84,6 +84,9 @@ public class SmHotelOrderDetailVo { private BigDecimal latitude; private Long deptId; + + @Schema(description = "酒店归属部门名称(商户)") + private String deptName; } @Data diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/SmHotelAppController.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/SmHotelAppController.java index 3560e123..3d06c750 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/SmHotelAppController.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/SmHotelAppController.java @@ -41,7 +41,6 @@ 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; @@ -91,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:v13:"; + private static final String PAGE_CACHE_PREFIX = "sm:hotel:app:page:v14:"; private static final long PAGE_CACHE_SECONDS = 45L; @@ -425,16 +424,32 @@ public class SmHotelAppController { } /** - * 按酒店名搜索: - * 1) 前缀匹配(idx_hotel_name + ORDER BY,快); - * 2) 无结果再含匹配——SQL 不做 ORDER BY,只 LIMIT,再内存排序(避免超时变空列表)。 - * 有城市时尽量落到 district_adcode 区间。 + * 按酒店名搜索(只打 canteen_hotel,几万级): + * 1) 先全国店名查出候选(前缀 → 含匹配),不带区划、不带日历,避免「有城市但酒店未落 adcode」直接变空; + * 2) 有城市时内存把本市排前面;本市一个都没有则仍返回全国店名命中(总比超时/无语强); + * 3) 日历最低价仍只查本页 id。 */ private Page pageByHotelNameKeyword(Page page, String keyword, String city, String district, Long districtAdcode, List 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)); + int fetch = KEYWORD_CONTAINS_FETCH; + long tName = System.currentTimeMillis(); + List rows = hotelMapper.selectAppByNamePrefix(keyword, fetch); + String hit = "prefix"; + if (rows == null || rows.isEmpty()) { + rows = hotelMapper.selectAppByNameContains(keyword, fetch); + hit = "contains"; + } + if (rows == null) { + rows = List.of(); + } + Set starSet = starList == null || starList.isEmpty() ? Set.of() : new HashSet<>(starList); + if (!starSet.isEmpty()) { + rows = rows.stream() + .filter(h -> h.getStarLevel() != null && starSet.contains(h.getStarLevel())) + .toList(); + } Long adFrom = null; Long adTo = null; Long adcode = districtAdcode; @@ -449,20 +464,7 @@ public class SmHotelAppController { adFrom = range[0]; adTo = range[1]; } - Collection stars = starList == null || starList.isEmpty() ? null : starList; - List 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 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())); + List sorted = rankKeywordHits(rows, adFrom, adTo); int from = (int) Math.min((current - 1) * size, sorted.size()); int to = (int) Math.min(from + size, sorted.size()); List slice = from >= sorted.size() ? List.of() : sorted.subList(from, to); @@ -470,6 +472,46 @@ public class SmHotelAppController { long total = (current - 1) * size + slice.size() + (hasMore ? 1 : 0); Page out = new Page<>(current, size, total); out.setRecords(new ArrayList<>(slice)); + log.info("|酒店App|店名搜索|kw={}|hit={}|cand={}|page={}|adcode={}|costMs={}|", keyword, hit, sorted.size(), + slice.size(), adcode, System.currentTimeMillis() - tName); + return out; + } + + /** 本市(adcode 命中)优先,再按星级、id;本市没有则保留全国店名命中 */ + private static List rankKeywordHits(List rows, Long adFrom, Long adTo) { + if (rows == null || rows.isEmpty()) { + return List.of(); + } + List inCity = new ArrayList<>(); + List other = new ArrayList<>(); + for (SmHotelEntity h : rows) { + if (h == null) { + continue; + } + if (adFrom != null && adTo != null && h.getDistrictAdcode() != null + && h.getDistrictAdcode() >= adFrom && h.getDistrictAdcode() < adTo) { + inCity.add(h); + } + else { + other.add(h); + } + } + Comparator byStar = Comparator + .comparing((SmHotelEntity h) -> h.getStarLevel() == null ? 0 : h.getStarLevel()) + .reversed() + .thenComparing((SmHotelEntity h) -> h.getId() == null ? 0L : h.getId(), Comparator.reverseOrder()); + inCity.sort(byStar); + other.sort(byStar); + List out = new ArrayList<>(inCity.size() + other.size()); + out.addAll(inCity); + // 本市一个都没有时,用全国店名结果,避免「库里有如家、界面无语」 + if (inCity.isEmpty()) { + out.addAll(other); + } + else { + // 本市有结果时仍可附带少量市外,便于翻页;首页优先本市 + out.addAll(other); + } return out; } diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/mapper/SmHotelMapper.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/mapper/SmHotelMapper.java index ac7a066c..cccfd0b9 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/mapper/SmHotelMapper.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/mapper/SmHotelMapper.java @@ -6,7 +6,6 @@ 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 @@ -15,58 +14,36 @@ public interface SmHotelMapper extends PjcloudBaseMapper { int insertBatch(@Param("list") List list); /** - * App 店名前缀搜索:走 idx_hotel_name,可带区划区间;带 ORDER BY 安全(前缀命中集小)。 + * 仅酒店表:店名前缀(走 idx_hotel_name)。几万级酒店 + LIMIT,秒级内。 */ @Select(""" - - """) - List selectAppByNamePrefix(@Param("keyword") String keyword, @Param("adFrom") Long adFrom, - @Param("adTo") Long adTo, @Param("starLevels") Collection starLevels, - @Param("limit") int limit); - - /** - * App 店名包含搜索:禁止 ORDER BY(含匹配 + 排序 = 全表 filesort 超时)。 - * 先 LIMIT 取候选,由调用方内存排序。有 adcode 时扫描范围极小。 - */ - @Select(""" - """) - List selectAppByNameContains(@Param("keyword") String keyword, @Param("adFrom") Long adFrom, - @Param("adTo") Long adTo, @Param("starLevels") Collection starLevels, - @Param("limit") int limit); + List selectAppByNamePrefix(@Param("keyword") String keyword, @Param("limit") int limit); + + /** + * 仅酒店表:店名包含。无 ORDER BY(避免 filesort);LIMIT 后内存排序。 + * 几万酒店全表扫含匹配通常远快于带日历/区划的复杂 SQL。 + */ + @Select(""" + 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}, '%') + LIMIT #{limit} + """) + List selectAppByNameContains(@Param("keyword") String keyword, @Param("limit") int limit); } diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmHotelOrderServiceImpl.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmHotelOrderServiceImpl.java index ecb2acc9..f894cf35 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmHotelOrderServiceImpl.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/SmHotelOrderServiceImpl.java @@ -26,6 +26,8 @@ import com.bsd.pjcloud.supermarket.service.SmHotelOrderUpstreamLogService; import com.bsd.pjcloud.supermarket.service.SmHotelPriceConfigService; import com.bsd.pjcloud.supermarket.vo.SmHotelOrderDetailVo; import com.bsd.pjcloud.supermarket.vo.SmHotelOrderPayInfoVo; +import com.bsd.pjcloud.admin.api.entity.SysDept; +import com.bsd.pjcloud.admin.api.feign.RemoteDeptService; import com.bsd.pjcloud.common.core.util.R; import com.bsd.pjcloud.common.data.tenant.TenantBroker; import com.bsd.pjcloud.common.data.tenant.TenantContextHolder; @@ -108,6 +110,8 @@ public class SmHotelOrderServiceImpl extends ServiceImpl pageOrders(Page page, SmHotelOrderEntity query) { Page result = baseMapper.selectPage(page, Wrappers.lambdaQuery() @@ -343,6 +347,7 @@ public class SmHotelOrderServiceImpl extends ServiceImpl records) { + List deptIds = records.stream() + .map(SmHotelOrderEntity::getDeptId) + .filter(Objects::nonNull) + .distinct() + .collect(Collectors.toList()); + if (deptIds.isEmpty()) { + return; + } + Map nameById = new HashMap<>(); + try { + R> r = remoteDeptService.getDeptByDeptIds(deptIds); + if (r != null && r.isOk() && r.getData() != null) { + r.getData().forEach((id, d) -> { + if (id != null && d != null && StrUtil.isNotBlank(d.getName())) { + nameById.put(id, d.getName()); + } + }); + } + } + catch (Exception e) { + log.warn("|酒店订单|补全部门名失败|{}", e.getMessage()); + } + for (SmHotelOrderEntity o : records) { + if (o.getDeptId() == null) { + continue; + } + o.setDeptName(nameById.getOrDefault(o.getDeptId(), String.valueOf(o.getDeptId()))); + } } private static void indexOpenAppNames(Map partnerNameMap, @@ -522,10 +560,31 @@ public class SmHotelOrderServiceImpl extends ServiceImpl> r = remoteDeptService.getDeptByDeptIds(List.of(hotel.getDeptId())); + if (r != null && r.isOk() && r.getData() != null) { + SysDept d = r.getData().get(hotel.getDeptId()); + if (d != null && StrUtil.isNotBlank(d.getName())) { + hb.setDeptName(d.getName()); + } + } + } + catch (Exception e) { + log.warn("|酒店订单详情|补全部门名失败|deptId={}|{}", hotel.getDeptId(), e.getMessage()); + } + if (StrUtil.isBlank(hb.getDeptName())) { + hb.setDeptName(String.valueOf(hotel.getDeptId())); + } + } vo.setHotel(hb); if (StrUtil.isBlank(order.getHotelName())) { order.setHotelName(hotel.getHotelName()); } + if (order.getDeptId() == null) { + order.setDeptId(hotel.getDeptId()); + order.setDeptName(hb.getDeptName()); + } } SmHotelProductEntity product = null;