Merge branch 'master' of https://gitea.zianxin.com/zax/biz-api-cloud-zax
This commit is contained in:
commit
e407fe3da6
8 files changed
+658
-10
No files matched your search
+18
@@ -71,6 +71,24 @@ public class SmHotelEntity extends Model<SmHotelEntity> {
|
||||
@Schema(description = "详细地址")
|
||||
private String address;
|
||||
|
||||
@Schema(description = "位置区域名称(如南湖区,对应 sys_area.name)")
|
||||
private String district;
|
||||
|
||||
@Schema(description = "位置区域编码(sys_area.adcode,区县)")
|
||||
private Long districtAdcode;
|
||||
|
||||
@Schema(description = "商圈/地标(如嘉兴南站附近)")
|
||||
private String bizArea;
|
||||
|
||||
@Schema(description = "星级档次:1民宿 2经济 3舒适 4高档 5五星")
|
||||
private Integer starLevel;
|
||||
|
||||
@Schema(description = "评分(0.0~5.0)")
|
||||
private BigDecimal score;
|
||||
|
||||
@Schema(description = "设施标签,逗号分隔")
|
||||
private String facilityTags;
|
||||
|
||||
@Schema(description = "经度")
|
||||
private BigDecimal longitude;
|
||||
|
||||
|
||||
+482
@@ -0,0 +1,482 @@
|
||||
package com.bsd.pjcloud.supermarket.controller;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.toolkit.Db;
|
||||
import com.bsd.pjcloud.common.api.encrypt.annotation.crypto.ApiCryptoAes;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.common.security.service.PjcloudUser;
|
||||
import com.bsd.pjcloud.common.security.util.SecurityUtils;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelDailyEntity;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelEntity;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelOrderEntity;
|
||||
import com.bsd.pjcloud.supermarket.entity.SmHotelProductEntity;
|
||||
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.vo.SmHotelDetailVO;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springdoc.core.annotations.ParameterObject;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 移动端 TOC 酒店预订(列表/详情/下单/我的订单)。
|
||||
*/
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/app/hotel")
|
||||
@Tag(name = "移动端酒店预订")
|
||||
@ApiCryptoAes
|
||||
public class SmHotelAppController {
|
||||
|
||||
/** 星级档次文案:1民宿 2经济 3舒适 4高档 5五星 */
|
||||
private static final Map<Integer, String> STAR_LABEL = Map.of(1, "民宿", 2, "经济", 3, "舒适", 4, "高档", 5, "五星");
|
||||
|
||||
private static final List<String> DEFAULT_FACILITY_OPTIONS = List.of("含早餐", "免费取消", "免费停车", "立即确认",
|
||||
"限时优惠", "接站服务");
|
||||
|
||||
private final SmHotelService hotelService;
|
||||
|
||||
private final SmHotelMidService midService;
|
||||
|
||||
private final SmHotelOrderService orderService;
|
||||
|
||||
@Operation(summary = "列表筛选项(区域/星级/设施)")
|
||||
@GetMapping("/filter-meta")
|
||||
public R<Map<String, Object>> filterMeta() {
|
||||
var online = 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);
|
||||
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())) {
|
||||
districts.add(h.getDistrict().trim());
|
||||
}
|
||||
if (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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
List<Map<String, Object>> starLevels = new ArrayList<>();
|
||||
STAR_LABEL.forEach((k, v) -> {
|
||||
Map<String, Object> item = new LinkedHashMap<>();
|
||||
item.put("value", k);
|
||||
item.put("label", v);
|
||||
starLevels.add(item);
|
||||
});
|
||||
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);
|
||||
out.put("facilityTags", new ArrayList<>(facilities));
|
||||
out.put("scoreOptions", List.of(4.5, 4.0, 3.5, 3.0));
|
||||
out.put("priceOptions", List.of(
|
||||
Map.of("label", "150以下", "min", 0, "max", 150),
|
||||
Map.of("label", "150-300", "min", 150, "max", 300),
|
||||
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);
|
||||
}
|
||||
|
||||
@Operation(summary = "上架酒店分页(关键字/区域/星级/评分/价格/设施)")
|
||||
@GetMapping("/page")
|
||||
public R<Page<Map<String, Object>>> page(@ParameterObject Page<SmHotelEntity> page,
|
||||
@RequestParam(required = false) String keyword,
|
||||
@RequestParam(required = false) String city,
|
||||
@RequestParam(required = false) String district,
|
||||
@RequestParam(required = false) Long districtAdcode,
|
||||
@RequestParam(required = false) String bizArea,
|
||||
@RequestParam(required = false) String starLevels,
|
||||
@RequestParam(required = false) BigDecimal minScore,
|
||||
@RequestParam(required = false) BigDecimal minPrice,
|
||||
@RequestParam(required = false) BigDecimal maxPrice,
|
||||
@RequestParam(required = false) String facilityTags) {
|
||||
List<Integer> starList = parseIntCsv(starLevels);
|
||||
List<String> facilityList = parseStrCsv(facilityTags);
|
||||
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()))
|
||||
.eq(districtAdcode == null && StrUtil.isNotBlank(district), SmHotelEntity::getDistrict, StrUtil.trim(district));
|
||||
// district 兼作区域名称(选省/市未选下级时传「浙江省」等),与 adcode 前缀一起匹配
|
||||
applyAreaAdcode(wrapper, districtAdcode, district);
|
||||
wrapper.like(StrUtil.isNotBlank(bizArea), SmHotelEntity::getBizArea, StrUtil.trim(bizArea))
|
||||
.in(!starList.isEmpty(), SmHotelEntity::getStarLevel, starList)
|
||||
.ge(minScore != null, SmHotelEntity::getScore, minScore)
|
||||
.orderByDesc(SmHotelEntity::getScore)
|
||||
.orderByDesc(SmHotelEntity::getUpdateTime)
|
||||
.orderByDesc(SmHotelEntity::getId);
|
||||
for (String tag : facilityList) {
|
||||
wrapper.like(SmHotelEntity::getFacilityTags, tag);
|
||||
}
|
||||
// 起价区间:按产品最低卖价过滤(分页前约束)
|
||||
if (minPrice != null || maxPrice != null) {
|
||||
List<Long> priceHitIds = loadHotelIdsByMinPriceRange(minPrice, maxPrice);
|
||||
if (priceHitIds.isEmpty()) {
|
||||
Page<Map<String, Object>> empty = new Page<>(page.getCurrent(), page.getSize(), 0);
|
||||
empty.setRecords(List.of());
|
||||
return R.ok(empty);
|
||||
}
|
||||
wrapper.in(SmHotelEntity::getId, priceHitIds);
|
||||
}
|
||||
// 移动端 TOC 不走管理端数据权限,按租户查已上架酒店
|
||||
Page<SmHotelEntity> raw = hotelService.page(page, wrapper);
|
||||
List<Long> ids = raw.getRecords().stream().map(SmHotelEntity::getId).filter(Objects::nonNull).toList();
|
||||
Map<Long, BigDecimal> minPriceMap = loadMinSellPrice(ids);
|
||||
Page<Map<String, Object>> out = new Page<>(raw.getCurrent(), raw.getSize(), raw.getTotal());
|
||||
out.setRecords(raw.getRecords().stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(h -> toListRow(h, minPriceMap.get(h.getId())))
|
||||
.toList());
|
||||
return R.ok(out);
|
||||
}
|
||||
|
||||
@Operation(summary = "酒店详情(含房型/产品)")
|
||||
@GetMapping("/detail/{id}")
|
||||
public R<SmHotelDetailVO> detail(@PathVariable Long id) {
|
||||
R<SmHotelDetailVO> r = midService.getHotelDetail(id);
|
||||
if (r.getCode() != 0 || r.getData() == null || r.getData().getHotel() == null) {
|
||||
return r;
|
||||
}
|
||||
if ("OFFLINE".equalsIgnoreCase(r.getData().getHotel().getUpstreamStatus())) {
|
||||
return R.failed("酒店已下架");
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
@Operation(summary = "酒店日历分页")
|
||||
@GetMapping("/detail/{id}/daily")
|
||||
public R<Page<SmHotelDailyEntity>> daily(@PathVariable Long id,
|
||||
@ParameterObject Page<SmHotelDailyEntity> page,
|
||||
@RequestParam(required = false) String productCode) {
|
||||
SmHotelEntity hotel = hotelService.getById(id);
|
||||
if (hotel == null) {
|
||||
return R.failed("酒店不存在");
|
||||
}
|
||||
if ("OFFLINE".equalsIgnoreCase(hotel.getUpstreamStatus())) {
|
||||
return R.failed("酒店已下架");
|
||||
}
|
||||
return midService.pageDaily(id, page, productCode);
|
||||
}
|
||||
|
||||
@Operation(summary = "提交预订")
|
||||
@PostMapping("/order/create")
|
||||
public R<JSONObject> createOrder(@RequestBody Map<String, Object> body) {
|
||||
PjcloudUser user = SecurityUtils.getUser();
|
||||
if (user == null) {
|
||||
return R.failed("请先登录");
|
||||
}
|
||||
Map<String, Object> req = body == null ? new HashMap<>() : new HashMap<>(body);
|
||||
if (StrUtil.isBlank(str(req.get("channelNo")))) {
|
||||
req.put("channelNo", "APP" + user.getId() + System.currentTimeMillis());
|
||||
}
|
||||
if (StrUtil.isBlank(str(req.get("contactName")))) {
|
||||
req.put("contactName", StrUtil.blankToDefault(user.getName(), user.getUsername()));
|
||||
}
|
||||
if (StrUtil.isBlank(str(req.get("contactPhone")))) {
|
||||
req.put("contactPhone", user.getPhone());
|
||||
}
|
||||
return orderService.createByAdmin(req);
|
||||
}
|
||||
|
||||
@Operation(summary = "我的酒店订单分页")
|
||||
@GetMapping("/order/page")
|
||||
public R<Page<Map<String, Object>>> myOrders(@ParameterObject Page<SmHotelOrderEntity> page,
|
||||
@RequestParam(required = false) String orderStatus) {
|
||||
PjcloudUser user = SecurityUtils.getUser();
|
||||
if (user == null) {
|
||||
return R.failed("请先登录");
|
||||
}
|
||||
SmHotelOrderEntity query = new SmHotelOrderEntity();
|
||||
query.setOrderStatus(StrUtil.trimToNull(orderStatus));
|
||||
query.setCreateBy(user.getUsername());
|
||||
Page<SmHotelOrderEntity> raw = orderService.pageOrders(page, query);
|
||||
List<Long> hotelIds = raw.getRecords().stream()
|
||||
.map(SmHotelOrderEntity::getHotelId)
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.toList();
|
||||
Map<Long, String> hotelNameMap = new HashMap<>();
|
||||
if (!hotelIds.isEmpty()) {
|
||||
List<SmHotelEntity> hotels = hotelService.listByIds(hotelIds);
|
||||
for (SmHotelEntity h : hotels) {
|
||||
hotelNameMap.put(h.getId(), h.getHotelName());
|
||||
}
|
||||
}
|
||||
Page<Map<String, Object>> out = new Page<>(raw.getCurrent(), raw.getSize(), raw.getTotal());
|
||||
out.setRecords(raw.getRecords().stream().map(o -> {
|
||||
Map<String, Object> row = new LinkedHashMap<>();
|
||||
row.put("id", o.getId());
|
||||
row.put("channelNo", o.getChannelNo());
|
||||
row.put("platformNo", o.getPlatformNo());
|
||||
row.put("hotelId", o.getHotelId());
|
||||
row.put("hotelCode", o.getHotelCode());
|
||||
row.put("hotelName", hotelNameMap.get(o.getHotelId()));
|
||||
row.put("productCode", o.getProductCode());
|
||||
row.put("roomTypeCode", o.getRoomTypeCode());
|
||||
row.put("checkInDate", o.getCheckInDate());
|
||||
row.put("checkOutDate", o.getCheckOutDate());
|
||||
row.put("count", o.getCount());
|
||||
row.put("sellAmount", o.getSellAmount());
|
||||
row.put("orderStatus", o.getOrderStatus());
|
||||
row.put("bookStatus", o.getBookStatus());
|
||||
row.put("contactName", o.getContactName());
|
||||
row.put("contactPhone", o.getContactPhone());
|
||||
row.put("createTime", o.getCreateTime());
|
||||
return row;
|
||||
}).toList());
|
||||
return R.ok(out);
|
||||
}
|
||||
|
||||
@Operation(summary = "我的订单详情")
|
||||
@GetMapping("/order/{id}")
|
||||
public R<SmHotelOrderEntity> orderDetail(@PathVariable Long id) {
|
||||
PjcloudUser user = SecurityUtils.getUser();
|
||||
if (user == null) {
|
||||
return R.failed("请先登录");
|
||||
}
|
||||
SmHotelOrderEntity order = orderService.getDetail(id);
|
||||
if (order == null) {
|
||||
return R.failed("订单不存在");
|
||||
}
|
||||
if (!user.getUsername().equals(order.getCreateBy())) {
|
||||
return R.failed("无权查看该订单");
|
||||
}
|
||||
return R.ok(order);
|
||||
}
|
||||
|
||||
@Operation(summary = "取消我的订单")
|
||||
@PostMapping("/order/cancel/{id}")
|
||||
public R<Boolean> cancelOrder(@PathVariable Long id, @RequestBody(required = false) Map<String, Object> body) {
|
||||
PjcloudUser user = SecurityUtils.getUser();
|
||||
if (user == null) {
|
||||
return R.failed("请先登录");
|
||||
}
|
||||
SmHotelOrderEntity order = orderService.getDetail(id);
|
||||
if (order == null) {
|
||||
return R.failed("订单不存在");
|
||||
}
|
||||
if (!user.getUsername().equals(order.getCreateBy())) {
|
||||
return R.failed("无权取消该订单");
|
||||
}
|
||||
String reason = body == null ? "用户取消" : String.valueOf(body.getOrDefault("reason", "用户取消"));
|
||||
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) {
|
||||
return row;
|
||||
}
|
||||
row.put("id", h.getId());
|
||||
row.put("hotelName", h.getHotelName());
|
||||
row.put("hotelCode", h.getHotelCode());
|
||||
row.put("address", h.getAddress());
|
||||
row.put("district", h.getDistrict());
|
||||
row.put("districtAdcode", h.getDistrictAdcode());
|
||||
row.put("bizArea", h.getBizArea());
|
||||
row.put("starLevel", h.getStarLevel());
|
||||
row.put("starLabel", starLabel(h.getStarLevel()));
|
||||
row.put("score", h.getScore());
|
||||
row.put("facilityTags", h.getFacilityTags());
|
||||
row.put("coverImage", h.getCoverImage());
|
||||
row.put("phone", h.getPhone());
|
||||
row.put("sheetName", h.getSheetName());
|
||||
row.put("minSellPrice", minSellPrice);
|
||||
return row;
|
||||
}
|
||||
|
||||
private Map<Long, BigDecimal> loadMinSellPrice(List<Long> hotelIds) {
|
||||
Map<Long, BigDecimal> map = new HashMap<>();
|
||||
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) {
|
||||
continue;
|
||||
}
|
||||
BigDecimal old = map.get(p.getHotelId());
|
||||
if (old == null || p.getSellPrice().compareTo(old) < 0) {
|
||||
map.put(p.getHotelId(), p.getSellPrice());
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/** 按产品最低卖价落在区间内的酒店 id(起价筛选) */
|
||||
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;
|
||||
}
|
||||
|
||||
private static List<Integer> parseIntCsv(String csv) {
|
||||
if (StrUtil.isBlank(csv)) {
|
||||
return List.of();
|
||||
}
|
||||
return Arrays.stream(csv.split("[,,]"))
|
||||
.map(String::trim)
|
||||
.filter(StrUtil::isNotBlank)
|
||||
.map(s -> {
|
||||
try {
|
||||
return Integer.parseInt(s);
|
||||
}
|
||||
catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static List<String> parseStrCsv(String csv) {
|
||||
if (StrUtil.isBlank(csv)) {
|
||||
return List.of();
|
||||
}
|
||||
return Arrays.stream(csv.split("[,,]"))
|
||||
.map(String::trim)
|
||||
.filter(StrUtil::isNotBlank)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static String starLabel(Integer starLevel) {
|
||||
return starLevel == null ? null : STAR_LABEL.get(starLevel);
|
||||
}
|
||||
|
||||
private static String str(Object o) {
|
||||
return o == null ? null : String.valueOf(o);
|
||||
}
|
||||
}
|
||||
+8
-2
@@ -77,6 +77,7 @@ public class SmHotelOrderServiceImpl extends ServiceImpl<SmHotelOrderMapper, SmH
|
||||
.eq(StrUtil.isNotBlank(query.getCompensateStatus()), SmHotelOrderEntity::getCompensateStatus,
|
||||
query.getCompensateStatus())
|
||||
.eq(StrUtil.isNotBlank(query.getHotelCode()), SmHotelOrderEntity::getHotelCode, query.getHotelCode())
|
||||
.eq(StrUtil.isNotBlank(query.getCreateBy()), SmHotelOrderEntity::getCreateBy, query.getCreateBy())
|
||||
.orderByDesc(SmHotelOrderEntity::getCreateTime));
|
||||
}
|
||||
|
||||
@@ -241,8 +242,13 @@ public class SmHotelOrderServiceImpl extends ServiceImpl<SmHotelOrderMapper, SmH
|
||||
if (product == null) {
|
||||
return R.failed("产品不存在,请先拉取酒店详情");
|
||||
}
|
||||
if (StrUtil.isBlank(product.getRoomTypeCode()) || StrUtil.isBlank(product.getPriceCode())) {
|
||||
return R.failed("产品缺少房型码或房价码,请重新拉取上游详情");
|
||||
boolean localHotelPreview = SmHotelDataSource.isLocal(hotel.getSheetName());
|
||||
if (StrUtil.isBlank(product.getRoomTypeCode())) {
|
||||
return R.failed("产品缺少房型码");
|
||||
}
|
||||
// 第三方必须有价格码;本地可手工维护,价格码可空
|
||||
if (!localHotelPreview && StrUtil.isBlank(product.getPriceCode())) {
|
||||
return R.failed("产品缺少房价码,请重新拉取上游详情");
|
||||
}
|
||||
LocalDate checkIn = parseDate(body.get("checkInDate"));
|
||||
LocalDate checkOut = parseDate(body.get("checkOutDate"));
|
||||
|
||||
+15
@@ -425,8 +425,23 @@ public class SmHotelServiceImpl extends ServiceImpl<SmHotelMapper, SmHotelEntity
|
||||
entity.setCoverImage(trimToNull(entity.getCoverImage()));
|
||||
entity.setCarouselImages(trimToNull(entity.getCarouselImages()));
|
||||
entity.setAddress(trimToNull(entity.getAddress()));
|
||||
entity.setDistrict(trimToNull(entity.getDistrict()));
|
||||
entity.setBizArea(trimToNull(entity.getBizArea()));
|
||||
entity.setFacilityTags(trimToNull(entity.getFacilityTags()));
|
||||
if (entity.getDistrictAdcode() != null && entity.getDistrictAdcode() <= 0) {
|
||||
entity.setDistrictAdcode(null);
|
||||
}
|
||||
entity.setPhone(trimToNull(entity.getPhone()));
|
||||
entity.setIntro(trimToNull(entity.getIntro()));
|
||||
if (entity.getStarLevel() != null && (entity.getStarLevel() < 1 || entity.getStarLevel() > 5)) {
|
||||
entity.setStarLevel(null);
|
||||
}
|
||||
if (entity.getScore() != null) {
|
||||
if (entity.getScore().compareTo(java.math.BigDecimal.ZERO) < 0
|
||||
|| entity.getScore().compareTo(new java.math.BigDecimal("5.0")) > 0) {
|
||||
entity.setScore(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 本地滚加仅数据来源=local 有效;天数钳制 1~366 */
|
||||
|
||||
+10
@@ -125,4 +125,14 @@ public class SysAreaEntity extends Model<SysAreaEntity> {
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
@Schema(description = "删除标记")
|
||||
private String delFlag;
|
||||
|
||||
/** 列表展示:父级地区名称(非表字段) */
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "父级地区名称")
|
||||
private String parentName;
|
||||
|
||||
/** 列表展示:是否有下级(非表字段) */
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "是否有下级")
|
||||
private Boolean hasChildren;
|
||||
}
|
||||
+31
@@ -62,6 +62,37 @@ public class SysAreaController {
|
||||
return R.ok(sysAreaService.selectTree(sysArea));
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动端/TOC:下级区域列表(登录即可,无需行政区划管理权限)
|
||||
*/
|
||||
@Operation(summary = "App下级区域列表")
|
||||
@GetMapping("/app/children")
|
||||
public R listAppChildren(@RequestParam(required = false) Long pid,
|
||||
@RequestParam(required = false) String areaType,
|
||||
@RequestParam(required = false) String keyword) {
|
||||
return R.ok(sysAreaService.listChildren(pid, areaType, keyword));
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动端/TOC:按名称查区域(城市定位等)
|
||||
*/
|
||||
@Operation(summary = "App按名称查区域")
|
||||
@GetMapping("/app/search")
|
||||
public R searchApp(@RequestParam String name,
|
||||
@RequestParam(required = false) String areaType,
|
||||
@RequestParam(required = false) Integer limit) {
|
||||
return R.ok(sysAreaService.listByName(name, areaType, limit));
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动端/TOC:懒加载树(同管理端 tree,登录即可)
|
||||
*/
|
||||
@Operation(summary = "App区域树")
|
||||
@GetMapping("/app/tree")
|
||||
public R getAppTree(@ParameterObject SysAreaEntity sysArea) {
|
||||
return R.ok(sysAreaService.selectTree(sysArea));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取详细信息
|
||||
*
|
||||
|
||||
+14
-1
@@ -18,11 +18,24 @@ public interface SysAreaService extends IService<SysAreaEntity> {
|
||||
List<Tree<Long>> selectTree(SysAreaEntity sysArea);
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
* 分页查询(附带父级名称)
|
||||
*
|
||||
* @param page 分页对象
|
||||
* @param sysArea 行政区划
|
||||
* @return Page
|
||||
*/
|
||||
Page selectPage(Page page, SysAreaEntity sysArea);
|
||||
|
||||
/**
|
||||
* App/联动:按父编码查询启用中的下级(可按类型过滤)。
|
||||
* @param pid 父级 adcode,空则默认 100000(中国)
|
||||
* @param areaType 可选:0国家 1省 2市 3区县 4街道
|
||||
* @param keyword 名称模糊
|
||||
*/
|
||||
List<SysAreaEntity> listChildren(Long pid, String areaType, String keyword);
|
||||
|
||||
/**
|
||||
* 按名称精确/模糊查找区域(如城市名「嘉兴市」)
|
||||
*/
|
||||
List<SysAreaEntity> listByName(String name, String areaType, Integer limit);
|
||||
}
|
||||
+80
-7
@@ -18,8 +18,10 @@ import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 行政区划
|
||||
@@ -29,6 +31,9 @@ import java.util.Optional;
|
||||
*/
|
||||
@Service
|
||||
public class SysAreaServiceImpl extends ServiceImpl<SysAreaMapper, SysAreaEntity> implements SysAreaService {
|
||||
|
||||
private static final Long ROOT_ADCODE = 100000L;
|
||||
|
||||
/**
|
||||
* 查询行政区划树
|
||||
*
|
||||
@@ -48,27 +53,95 @@ public class SysAreaServiceImpl extends ServiceImpl<SysAreaMapper, SysAreaEntity
|
||||
|
||||
HashMap<String, Object> extraMap = MapUtil.of(SysAreaEntity.Fields.adcode, sysAreaEntity.getAdcode());
|
||||
extraMap.put(SysAreaEntity.Fields.hot, sysAreaEntity.getHot());
|
||||
extraMap.put(SysAreaEntity.Fields.areaType, sysAreaEntity.getAreaType());
|
||||
extraMap.put(SysAreaEntity.Fields.name, sysAreaEntity.getName());
|
||||
extraMap.put("id", sysAreaEntity.getAdcode());
|
||||
treeNode.setExtra(extraMap);
|
||||
nodeList.add(treeNode);
|
||||
}
|
||||
|
||||
return TreeUtil.build(nodeList, Optional.ofNullable(sysArea.getPid()).orElse(100000L));
|
||||
return TreeUtil.build(nodeList, Optional.ofNullable(sysArea.getPid()).orElse(ROOT_ADCODE));
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*
|
||||
* @param page 分页对象
|
||||
* @param sysArea 行政区划
|
||||
* @return Page
|
||||
* 分页查询(附带父级名称,便于管理端看清上下级)
|
||||
*/
|
||||
@Override
|
||||
public Page selectPage(Page page, SysAreaEntity sysArea) {
|
||||
LambdaQueryWrapper<SysAreaEntity> wrapper = Wrappers.lambdaQuery();
|
||||
wrapper.like(StrUtil.isNotBlank(sysArea.getName()), SysAreaEntity::getName, sysArea.getName());
|
||||
// 约定:入参 adcode 表示「查该节点下的直接子级」
|
||||
wrapper.eq(Objects.nonNull(sysArea.getAdcode()), SysAreaEntity::getPid, sysArea.getAdcode());
|
||||
wrapper.eq(StrUtil.isNotBlank(sysArea.getAreaType()), SysAreaEntity::getAreaType, sysArea.getAreaType());
|
||||
wrapper.orderByAsc(SysAreaEntity::getAreaType);
|
||||
wrapper.orderByDesc(SysAreaEntity::getAreaSort);
|
||||
return baseMapper.selectPage(page, wrapper);
|
||||
Page<SysAreaEntity> result = baseMapper.selectPage(page, wrapper);
|
||||
fillParentName(result.getRecords());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SysAreaEntity> listChildren(Long pid, String areaType, String keyword) {
|
||||
Long parent = pid == null ? ROOT_ADCODE : pid;
|
||||
// App/TOC 与后台列表对齐:不按 area_status 过滤,避免后台能见、移动端空白
|
||||
LambdaQueryWrapper<SysAreaEntity> wrapper = Wrappers.<SysAreaEntity>lambdaQuery()
|
||||
.eq(SysAreaEntity::getPid, parent)
|
||||
.eq(StrUtil.isNotBlank(areaType), SysAreaEntity::getAreaType, areaType)
|
||||
.like(StrUtil.isNotBlank(keyword), SysAreaEntity::getName, StrUtil.trim(keyword))
|
||||
.orderByDesc(SysAreaEntity::getHot)
|
||||
.orderByDesc(SysAreaEntity::getAreaSort)
|
||||
.orderByAsc(SysAreaEntity::getAdcode);
|
||||
List<SysAreaEntity> list = baseMapper.selectList(wrapper);
|
||||
fillParentName(list);
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SysAreaEntity> listByName(String name, String areaType, Integer limit) {
|
||||
if (StrUtil.isBlank(name)) {
|
||||
return List.of();
|
||||
}
|
||||
int size = limit == null || limit < 1 ? 20 : Math.min(limit, 100);
|
||||
return baseMapper.selectList(Wrappers.<SysAreaEntity>lambdaQuery()
|
||||
.eq(StrUtil.isNotBlank(areaType), SysAreaEntity::getAreaType, areaType)
|
||||
.and(w -> w.eq(SysAreaEntity::getName, name.trim())
|
||||
.or()
|
||||
.like(SysAreaEntity::getName, name.trim()))
|
||||
.orderByDesc(SysAreaEntity::getHot)
|
||||
.orderByAsc(SysAreaEntity::getAreaType)
|
||||
.last("limit " + size));
|
||||
}
|
||||
|
||||
private void fillParentName(List<SysAreaEntity> records) {
|
||||
if (CollUtil.isEmpty(records)) {
|
||||
return;
|
||||
}
|
||||
List<Long> pids = records.stream().map(SysAreaEntity::getPid).filter(Objects::nonNull).distinct().toList();
|
||||
Map<Long, String> nameMap = new HashMap<>();
|
||||
if (!pids.isEmpty()) {
|
||||
List<SysAreaEntity> parents = baseMapper.selectList(Wrappers.<SysAreaEntity>lambdaQuery()
|
||||
.in(SysAreaEntity::getAdcode, pids)
|
||||
.select(SysAreaEntity::getAdcode, SysAreaEntity::getName));
|
||||
nameMap = parents.stream()
|
||||
.collect(Collectors.toMap(SysAreaEntity::getAdcode, SysAreaEntity::getName, (a, b) -> a));
|
||||
}
|
||||
List<Long> adcodes = records.stream().map(SysAreaEntity::getAdcode).filter(Objects::nonNull).toList();
|
||||
Map<Long, Long> childCount = new HashMap<>();
|
||||
if (!adcodes.isEmpty()) {
|
||||
List<SysAreaEntity> children = baseMapper.selectList(Wrappers.<SysAreaEntity>lambdaQuery()
|
||||
.in(SysAreaEntity::getPid, adcodes)
|
||||
.select(SysAreaEntity::getPid));
|
||||
for (SysAreaEntity c : children) {
|
||||
if (c.getPid() != null) {
|
||||
childCount.merge(c.getPid(), 1L, Long::sum);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (SysAreaEntity row : records) {
|
||||
if (row.getPid() != null) {
|
||||
row.setParentName(nameMap.get(row.getPid()));
|
||||
}
|
||||
row.setHasChildren(childCount.getOrDefault(row.getAdcode(), 0L) > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user