This commit is contained in:
zjf committed 2026-10-22 18:09:04 +08:00
1 parent 2c02fb14e8
commit ae8d5d0835
11 files changed
+294 -37

No files matched your search

@@ -56,6 +56,9 @@ public class SmHotelDownstreamChannelConfigDTO {
@Schema(description = "product/daily 每分钟上限,默认 300;≤0 关闭")
private Integer rateLimitProductDailyPerMin;
@Schema(description = "慧订 hoteldatepush 出站 QPS,默认 5;≤0 关闭")
private Integer rateLimitHotelDatePushQps;
@Schema(description = "order/query 每分钟上限,默认 120;≤0 关闭")
private Integer rateLimitOrderQueryPerMin;
@@ -129,11 +129,16 @@ public class SmHotelOrderEntity extends Model<SmHotelOrderEntity> {
@Schema(description = "酒店名称")
private String hotelName;
/** 详情展示:产品名称(非表字段) */
/** 列表/详情展示:产品名称(非表字段) */
@TableField(exist = false)
@Schema(description = "产品名称")
private String productName;
/** 列表展示:房型名称(非表字段) */
@TableField(exist = false)
@Schema(description = "房型名称")
private String roomTypeName;
/** 列表展示:下单来源 APP/ADMIN/LOCAL/DOWNSTREAM(非表字段) */
@TableField(exist = false)
@Schema(description = "下单来源")
@@ -77,4 +77,9 @@ public class SmHotelProductEntity extends Model<SmHotelProductEntity> {
@TableLogic
@TableField(fill = FieldFill.INSERT)
private String delFlag;
/** App 详情:所选入住区间无日历/不可订时为 true(非表字段) */
@TableField(exist = false)
@Schema(description = "所选日期已售罄")
private Boolean soldOut;
}
@@ -70,6 +70,12 @@ public class HotelDownstreamProperties {
/** /product/sync、/daily/sync 每分钟上限(环启文档 300) */
private int rateLimitProductDailyPerMin = 300;
/**
* 慧订 /splider/hoteldatepush 出站 QPS(对方已限流)。
* 默认 5;≤0 关闭自我节流。多实例请按实例均分,例如 2 实例各配 2~3。
*/
private int rateLimitHotelDatePushQps = 5;
/** /order/query 每分钟上限(环启文档 120) */
private int rateLimitOrderQueryPerMin = 120;
@@ -87,7 +87,7 @@ public class SmHotelAppController {
private static final String CANDIDATE_CACHE_OVERFLOW = "OVERFLOW";
/** 列表页结果短缓存(同条件重复进入/下拉) */
private static final String PAGE_CACHE_PREFIX = "sm:hotel:app:page:v5:";
private static final String PAGE_CACHE_PREFIX = "sm:hotel:app:page:v6:";
private static final long PAGE_CACHE_SECONDS = 45L;
@@ -271,18 +271,23 @@ public class SmHotelAppController {
int candSize = -1;
boolean smallScope = scopedIds != null && !scopedIds.isEmpty() && scopedIds.size() <= MAX_SCOPED_HOTEL_IDS;
if (smallScope) {
List<Long> candidateIds = resolveAppListCandidateHotelIds(stayFrom, stayToExclusive, needCap, scopedIds);
candSize = candidateIds == null ? -1 : candidateIds.size();
if (candidateIds != null && candidateIds.isEmpty()) {
return R.ok(emptyPage(page));
}
if (candidateIds != null) {
wrapper.in(SmHotelEntity::getId, candidateIds);
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);
}
else {
// 全国:按评分分页 + 每家酒店 EXISTS 可订,不做扫描上限(可订的都会出现,只是排序靠后)
applyBookableHotelFilter(wrapper, stayFrom, stayToExclusive);
// 全国:上架酒店都出列表;所选日期无日历的在行上标已售罄,不再按可订日历过滤掉
applyGuestCapacityFilter(wrapper, needCap);
}
if (minPrice != null || maxPrice != null) {
@@ -297,7 +302,12 @@ public class SmHotelAppController {
.orderByDesc(SmHotelEntity::getId);
raw = pageWithoutCount(page, wrapper);
List<Long> ids = raw.getRecords().stream().map(SmHotelEntity::getId).filter(Objects::nonNull).toList();
Map<Long, BigDecimal> minPriceMap = loadMinSellPrice(ids);
LocalDate priceFrom = stayFrom != null ? stayFrom : LocalDate.now();
LocalDate priceToEx = stayToExclusive;
if (priceToEx == null || !priceToEx.isAfter(priceFrom)) {
priceToEx = priceFrom.plusDays(1);
}
Map<Long, BigDecimal> minPriceMap = loadMinSellPrice(ids, priceFrom, priceToEx);
Page<Map<String, Object>> out = new Page<>(raw.getCurrent(), raw.getSize(), raw.getTotal());
out.setRecords(raw.getRecords().stream()
.filter(Objects::nonNull)
@@ -454,7 +464,10 @@ public class SmHotelAppController {
@Operation(summary = "酒店详情(含房型/产品)")
@GetMapping("/detail/{id}")
public R<SmHotelDetailVO> detail(@PathVariable Long id) {
public R<SmHotelDetailVO> detail(@PathVariable Long id,
@RequestParam(required = false) String checkIn,
@RequestParam(required = false) String checkOut,
@RequestParam(required = false) Integer rooms) {
R<SmHotelDetailVO> r = midService.getHotelDetail(id);
if (r.getCode() != 0 || r.getData() == null || r.getData().getHotel() == null) {
return r;
@@ -462,31 +475,56 @@ public class SmHotelAppController {
if ("OFFLINE".equalsIgnoreCase(r.getData().getHotel().getUpstreamStatus())) {
return R.failed("酒店已下架");
}
// App 侧:没有未来日历的产品无法预订,不展示
hideProductsWithoutCalendar(r.getData());
// 无日历的产品仍展示,所选入住日标已售罄
markProductSoldOut(r.getData(), parseDay(checkIn), parseDay(checkOut), rooms);
return r;
}
/**
* 过滤无未来日历的产品。无日历则无法计价/占库存,列表展示无意义。
* 按入住半开区间给产品打已售罄:缺日历、关房或剩余库存不足。
*/
private void hideProductsWithoutCalendar(SmHotelDetailVO vo) {
private void markProductSoldOut(SmHotelDetailVO vo, LocalDate checkIn, LocalDate checkOut, Integer rooms) {
if (vo == null || vo.getHotel() == null || vo.getHotel().getId() == null
|| vo.getProducts() == null || vo.getProducts().isEmpty()) {
return;
}
List<SmHotelDailyEntity> rows = Db.lambdaQuery(SmHotelDailyEntity.class)
LocalDate from = checkIn != null ? checkIn : LocalDate.now();
LocalDate toEx = checkOut;
if (toEx == null || !toEx.isAfter(from)) {
toEx = from.plusDays(1);
}
int need = rooms == null || rooms < 1 ? 1 : Math.min(rooms, 20);
List<LocalDate> nights = new ArrayList<>();
for (LocalDate d = from; d.isBefore(toEx); d = d.plusDays(1)) {
nights.add(d);
}
List<SmHotelDailyEntity> rows = dailyMapper.selectList(Wrappers.<SmHotelDailyEntity>lambdaQuery()
.eq(SmHotelDailyEntity::getHotelId, vo.getHotel().getId())
.ge(SmHotelDailyEntity::getStayDate, LocalDate.now())
.select(SmHotelDailyEntity::getProductCode)
.list();
Set<String> withDaily = rows.stream()
.map(SmHotelDailyEntity::getProductCode)
.filter(StrUtil::isNotBlank)
.collect(Collectors.toCollection(LinkedHashSet::new));
vo.setProducts(vo.getProducts().stream()
.filter(p -> p != null && withDaily.contains(p.getProductCode()))
.collect(Collectors.toList()));
.ge(SmHotelDailyEntity::getStayDate, from)
.lt(SmHotelDailyEntity::getStayDate, toEx));
Map<String, Set<LocalDate>> bookable = new HashMap<>();
if (rows != null) {
for (SmHotelDailyEntity row : rows) {
if (row == null || StrUtil.isBlank(row.getProductCode()) || row.getStayDate() == null) {
continue;
}
int status = row.getDailyStatus() == null ? 1 : row.getDailyStatus();
int stock = row.getStock() == null ? 0 : row.getStock();
int sold = row.getSold() == null ? 0 : row.getSold();
if (status != 1 || stock - sold < need) {
continue;
}
bookable.computeIfAbsent(row.getProductCode(), k -> new java.util.HashSet<>()).add(row.getStayDate());
}
}
for (SmHotelProductEntity p : vo.getProducts()) {
if (p == null) {
continue;
}
Set<LocalDate> ok = bookable.getOrDefault(p.getProductCode(), Set.of());
boolean soldOut = nights.isEmpty() || nights.stream().anyMatch(d -> !ok.contains(d));
p.setSoldOut(soldOut);
}
}
@Operation(summary = "酒店日历分页")
@@ -698,15 +736,17 @@ public class SmHotelAppController {
row.put("phone", h.getPhone());
row.put("sheetName", h.getSheetName());
row.put("minSellPrice", minSellPrice);
row.put("soldOut", minSellPrice == null);
return row;
}
private Map<Long, BigDecimal> loadMinSellPrice(List<Long> hotelIds) {
/** 所选入住区间可订日历的最低卖价;没有可订日历则不返回,列表标已售罄 */
private Map<Long, BigDecimal> loadMinSellPrice(List<Long> hotelIds, LocalDate from, LocalDate toExclusive) {
Map<Long, BigDecimal> map = new HashMap<>();
if (hotelIds == null || hotelIds.isEmpty()) {
if (hotelIds == null || hotelIds.isEmpty() || from == null || toExclusive == null) {
return map;
}
List<Map<String, Object>> rows = productMapper.selectMinSellPriceByHotelIds(hotelIds);
List<Map<String, Object>> rows = dailyMapper.selectMinSellPriceByStayRange(from, toExclusive, hotelIds);
if (rows == null) {
return map;
}
@@ -14,8 +14,11 @@ import com.bsd.pjcloud.supermarket.integration.downstream.DownstreamPushLogConte
import com.bsd.pjcloud.supermarket.service.SmHotelDownstreamPushLogService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* 慧订下游:静态 pushStaticHotels + 价态 hoteldatepush(窗口全量覆盖)。
* 请求体带 key / supplierId,成功码为 0(与环启 200 不同)。
@@ -27,8 +30,15 @@ public class HuidingDownstreamHotelAdapter implements DownstreamHotelAdapter {
private static final String TAG = "下游推送[慧订]";
private static final String RATE_KEY = "sm:hotel:huiding:qps:";
private final SmHotelDownstreamPushLogService pushLogService;
private final StringRedisTemplate redisTemplate;
/** 本进程 hoteldatepush 下次可发时间 */
private volatile long datePushNextPermitAtMs = 0L;
@Override
public String protocol() {
return SmHotelDownstreamProtocol.HUIDING;
@@ -106,6 +116,30 @@ public class HuidingDownstreamHotelAdapter implements DownstreamHotelAdapter {
String action, String path, Object bodyObj) {
assertReady(partnerCode, props);
String bodyJson = bodyObj instanceof String ? (String) bodyObj : JSONUtil.toJsonStr(bodyObj);
int maxRetry = Math.max(0, props.getRateLimitMaxRetries());
long backoff = Math.max(1000L, props.getRateLimitBackoffMs());
CheckedException last = null;
for (int attempt = 0; attempt <= maxRetry; attempt++) {
acquireHotelDatePushPermit(partnerCode, props, path);
try {
return doPostOnce(partnerCode, props, hotelId, extHotelId, action, path, bodyJson);
}
catch (CheckedException e) {
last = e;
if (!isRateLimitedMsg(e.getMessage()) || attempt >= maxRetry) {
throw e;
}
long wait = backoff * (1L << attempt);
log.warn("|{}|限流退避|partner={}|path={}|attempt={}|waitMs={}|", TAG, partnerCode, path, attempt + 1,
wait);
sleepQuiet(wait);
}
}
throw last != null ? last : new CheckedException("慧订调用失败");
}
private JSONObject doPostOnce(String partnerCode, HotelDownstreamProperties props, Long hotelId, String extHotelId,
String action, String path, String bodyJson) {
String url = trimSlash(props.getBaseUrl()) + path;
long t0 = System.currentTimeMillis();
Integer httpStatus = null;
@@ -139,7 +173,10 @@ public class HuidingDownstreamHotelAdapter implements DownstreamHotelAdapter {
StrUtil.maxLength(trimmed, 180));
}
}
if (json != null) {
if (httpStatus != null && httpStatus == 429) {
errMsg = StrUtil.format("慧订限流 HTTP 429 [{}] body={}", path, StrUtil.maxLength(trimmed, 180));
}
else if (json != null) {
// 慧订:code=0 成功;兼容部分接口用 200
int bizCode = json.getInt("code", -1);
if (bizCode == 0 || bizCode == 200) {
@@ -214,6 +251,69 @@ public class HuidingDownstreamHotelAdapter implements DownstreamHotelAdapter {
}
}
/**
* hoteldatepush 出站 QPS:本进程匀速 + Redis 秒窗(多实例共享)。
* 配置 rate-limit-hotel-date-push-qps ≤0 关闭。
*/
private void acquireHotelDatePushPermit(String partnerCode, HotelDownstreamProperties props, String path) {
if (!isHotelDatePushPath(path)) {
return;
}
int qps = props.getRateLimitHotelDatePushQps();
if (qps <= 0) {
return;
}
long intervalMs = Math.max(1L, Math.round(1000.0 / qps));
synchronized (this) {
long now = System.currentTimeMillis();
if (datePushNextPermitAtMs > now) {
sleepQuiet(datePushNextPermitAtMs - now);
now = System.currentTimeMillis();
}
datePushNextPermitAtMs = Math.max(now, datePushNextPermitAtMs) + intervalMs;
}
int maxPerSec = Math.max(1, qps);
String partner = StrUtil.blankToDefault(partnerCode, "default");
for (int i = 0; i < 30; i++) {
long sec = System.currentTimeMillis() / 1000L;
String key = RATE_KEY + partner + ":" + sec;
Long n = redisTemplate.opsForValue().increment(key);
if (n != null && n == 1L) {
redisTemplate.expire(key, 3, TimeUnit.SECONDS);
}
if (n == null || n <= maxPerSec) {
return;
}
long wait = 1000L - (System.currentTimeMillis() % 1000L) + 5L;
sleepQuiet(wait);
}
log.warn("|{}|hoteldatepush QPS 等待超时仍继续|partner={}|qps={}|", TAG, partner, qps);
}
private static boolean isHotelDatePushPath(String path) {
return StrUtil.isNotBlank(path) && path.contains("hoteldatepush");
}
private static boolean isRateLimitedMsg(String msg) {
if (StrUtil.isBlank(msg)) {
return false;
}
return msg.contains("过于频繁") || msg.contains("限流") || msg.contains("429")
|| msg.toLowerCase().contains("too many") || msg.toLowerCase().contains("rate limit");
}
private static void sleepQuiet(long ms) {
if (ms <= 0) {
return;
}
try {
Thread.sleep(ms);
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public static String resolveKey(HotelDownstreamProperties props) {
if (props == null) {
return "";
@@ -58,6 +58,27 @@ public interface SmHotelDailyMapper extends PjcloudBaseMapper<SmHotelDailyEntity
List<Long> selectDistinctHotelIdsByStayRangeInHotels(@Param("fromDate") LocalDate fromDate,
@Param("toExclusive") LocalDate toExclusive, @Param("hotelIds") Collection<Long> hotelIds);
/**
* 入住半开区间内可订日历的最低卖价(开房且有剩余库存)。
*/
@Select("""
<script>
SELECT hotel_id AS hotelId, MIN(sell_price) AS minSell
FROM canteen_hotel_daily
WHERE del_flag = '0'
AND stay_date &gt;= #{fromDate}
AND stay_date &lt; #{toExclusive}
AND IFNULL(daily_status, 1) = 1
AND IFNULL(stock, 0) - IFNULL(sold, 0) &gt; 0
AND sell_price IS NOT NULL
AND hotel_id IN
<foreach collection="hotelIds" item="id" open="(" separator="," close=")">#{id}</foreach>
GROUP BY hotel_id
</script>
""")
List<Map<String, Object>> selectMinSellPriceByStayRange(@Param("fromDate") LocalDate fromDate,
@Param("toExclusive") LocalDate toExclusive, @Param("hotelIds") Collection<Long> hotelIds);
int insertBatch(@Param("list") List<SmHotelDailyEntity> list);
/**
@@ -344,6 +344,9 @@ public class SmHotelChannelConfigServiceImpl extends ServiceImpl<SmHotelChannelC
if (dto.getRateLimitProductDailyPerMin() != null) {
target.setRateLimitProductDailyPerMin(dto.getRateLimitProductDailyPerMin());
}
if (dto.getRateLimitHotelDatePushQps() != null) {
target.setRateLimitHotelDatePushQps(dto.getRateLimitHotelDatePushQps());
}
if (dto.getRateLimitOrderQueryPerMin() != null) {
target.setRateLimitOrderQueryPerMin(dto.getRateLimitOrderQueryPerMin());
}
@@ -913,6 +913,7 @@ public class SmHotelMidServiceImpl implements SmHotelMidService {
String protocol = props == null ? null : props.getProtocol();
String protocolLabel = SmHotelDownstreamProtocol.label(protocol);
row.put("protocol", SmHotelDownstreamProtocol.normalize(protocol));
row.put("orderPullEnabled", props != null && props.isOrderPullEnabled());
SmHotelChannelConfigEntity cfg = channelConfigService.getByChannel(code,
com.bsd.pjcloud.supermarket.constant.SmHotelChannelDirection.DOWNSTREAM);
String remark = cfg == null ? null : StrUtil.trim(cfg.getRemark());
@@ -210,7 +210,7 @@ public class SmHotelOrderServiceImpl extends ServiceImpl<SmHotelOrderMapper, SmH
}
/**
* 分页列表展示字段:酒店名称、数据来源、订单来源(App/后台/分销伙伴协议+编码/下游应用)。
* 分页列表展示字段:酒店名称、房型/产品名称、数据来源、订单来源(App/后台/分销伙伴协议+编码/下游应用)。
* 酒店数据来源 ≠ 订单销售来源:同一上游(如飞猪)可被多家下游售出,靠 partnerCode / downstreamAppId 区分。
*/
private void fillOrderListDisplay(List<SmHotelOrderEntity> records) {
@@ -290,6 +290,51 @@ public class SmHotelOrderServiceImpl extends ServiceImpl<SmHotelOrderMapper, SmH
indexOpenAppNames(partnerNameMap, partnerProtocolMap, app);
}
}
Map<String, SmHotelProductEntity> productByHotelCode = new HashMap<>();
Map<String, SmHotelProductEntity> productByCode = new HashMap<>();
List<String> productCodes = records.stream()
.map(SmHotelOrderEntity::getProductCode)
.filter(StrUtil::isNotBlank)
.distinct()
.collect(Collectors.toList());
if (!productCodes.isEmpty()) {
var productQ = Wrappers.<SmHotelProductEntity>lambdaQuery()
.in(SmHotelProductEntity::getProductCode, productCodes);
if (!hotelIds.isEmpty()) {
productQ.in(SmHotelProductEntity::getHotelId, hotelIds);
}
for (SmHotelProductEntity p : productMapper.selectList(productQ)) {
if (StrUtil.isNotBlank(p.getProductCode())) {
productByCode.putIfAbsent(p.getProductCode(), p);
if (p.getHotelId() != null) {
productByHotelCode.put(p.getHotelId() + "|" + p.getProductCode(), p);
}
}
}
}
Map<String, SmHotelRoomEntity> roomByHotelCode = new HashMap<>();
List<String> roomTypeCodes = new ArrayList<>(records.stream()
.map(SmHotelOrderEntity::getRoomTypeCode)
.filter(StrUtil::isNotBlank)
.distinct()
.collect(Collectors.toList()));
for (SmHotelProductEntity p : productByCode.values()) {
if (StrUtil.isNotBlank(p.getRoomTypeCode()) && !roomTypeCodes.contains(p.getRoomTypeCode())) {
roomTypeCodes.add(p.getRoomTypeCode());
}
}
if (!roomTypeCodes.isEmpty()) {
var roomQ = Wrappers.<SmHotelRoomEntity>lambdaQuery()
.in(SmHotelRoomEntity::getRoomTypeCode, roomTypeCodes);
if (!hotelIds.isEmpty()) {
roomQ.in(SmHotelRoomEntity::getHotelId, hotelIds);
}
for (SmHotelRoomEntity r : roomMapper.selectList(roomQ)) {
if (r.getHotelId() != null && StrUtil.isNotBlank(r.getRoomTypeCode())) {
roomByHotelCode.putIfAbsent(r.getHotelId() + "|" + r.getRoomTypeCode(), r);
}
}
}
for (SmHotelOrderEntity o : records) {
SmHotelEntity hotel = o.getHotelId() != null ? hotelById.get(o.getHotelId()) : null;
if (hotel == null && StrUtil.isNotBlank(o.getHotelCode())) {
@@ -299,6 +344,28 @@ public class SmHotelOrderServiceImpl extends ServiceImpl<SmHotelOrderMapper, SmH
o.setHotelName(hotel.getHotelName());
o.setHotelDataSource(upstreamGatewayRegistry.resolveChannelKey(hotel.getSheetName()));
}
SmHotelProductEntity product = null;
if (StrUtil.isNotBlank(o.getProductCode())) {
if (o.getHotelId() != null) {
product = productByHotelCode.get(o.getHotelId() + "|" + o.getProductCode());
}
if (product == null) {
product = productByCode.get(o.getProductCode());
}
}
if (product != null) {
o.setProductName(product.getProductName());
}
String roomTypeCode = StrUtil.blankToDefault(o.getRoomTypeCode(),
product == null ? null : product.getRoomTypeCode());
Long roomHotelId = o.getHotelId() != null ? o.getHotelId()
: (hotel == null ? null : hotel.getId());
if (roomHotelId != null && StrUtil.isNotBlank(roomTypeCode)) {
SmHotelRoomEntity room = roomByHotelCode.get(roomHotelId + "|" + roomTypeCode);
if (room != null) {
o.setRoomTypeName(room.getRoomTypeName());
}
}
SmHotelDownstreamAppEntity app = o.getDownstreamAppId() == null ? null
: appById.get(o.getDownstreamAppId());
if (app != null) {
@@ -2188,6 +2255,7 @@ public class SmHotelOrderServiceImpl extends ServiceImpl<SmHotelOrderMapper, SmH
order.setUpstreamRespJson(JSONUtil.toJsonStr(body));
order.setLastCompensateMsg(StrUtil.sub("状态回调更新 bookStatus=" + bookStatus, 0, 500));
baseMapper.updateById(order);
notifyOpenOrderCallback(order);
return Map.of("updated", true, "localOrderId", order.getId(), "orderStatus", order.getOrderStatus());
}
@@ -2304,6 +2372,7 @@ public class SmHotelOrderServiceImpl extends ServiceImpl<SmHotelOrderMapper, SmH
order.setFailMsg(StrUtil.sub(reason, 0, 200));
}
baseMapper.updateById(order);
notifyOpenOrderCallback(order);
return R.ok(true, "本地订单已取消并回补库存");
}
// 尚无上游/本地确认单号:未支付或支付后履约失败等,仅关本地单并回补预占
@@ -2319,12 +2388,14 @@ public class SmHotelOrderServiceImpl extends ServiceImpl<SmHotelOrderMapper, SmH
order.setFailMsg(StrUtil.sub(reason, 0, 200));
}
baseMapper.updateById(order);
notifyOpenOrderCallback(order);
return R.ok(true, PAY_UNPAID.equals(order.getPayStatus()) ? "未支付订单已取消" : "本地订单已关闭");
}
R<Boolean> cancelR = cancelUpstreamOrder(order, reason);
if (cancelR == null || !cancelR.isOk()) {
return R.failed(cancelR == null ? "取消失败" : cancelR.getMsg());
}
notifyOpenOrderCallback(baseMapper.selectById(order.getId()));
return R.ok(true);
}
@@ -2413,6 +2484,7 @@ public class SmHotelOrderServiceImpl extends ServiceImpl<SmHotelOrderMapper, SmH
order.setFailMsg(StrUtil.sub(reason, 0, 200));
}
baseMapper.updateById(order);
notifyOpenOrderCallback(order);
return true;
}
@@ -112,10 +112,10 @@ spring:
#
## ========== 下游开放平台(/third/party/open)==========
## 图片公网根地址:相对路径(如 /admin/sys-file/...)会拼此前缀后下发给下游
sm-hotel:
open:
# public-file-base-url: https://www.zianxin.com/api
public-file-base-url: http://127.0.0.1:9999
#sm-hotel:
# open:
# # public-file-base-url: https://www.zianxin.com/api
# public-file-base-url: http://127.0.0.1:9999
## ========== 下游销售方推送(环启等;优先走库表「渠道对接配置」direction=downstream)==========
## 本系统作为 A,主动 POST 到 B 的 /third/party/api/*;可配置多家;部门范围见「渠道部门映射」
@@ -135,6 +135,7 @@ sm-hotel:
# # --- 限流(也可写在伙伴 YAML,库表会覆盖这里)---
# rate-limit-hotel-apartment-per-min: 600 # hotel+apartment / 分钟
# rate-limit-product-daily-per-min: 300 # product+daily / 分钟
# rate-limit-hotel-date-push-qps: 5 # 慧订 hoteldatepush 出站 QPS;≤0 关闭
# rate-limit-order-query-per-min: 120 # order/query / 分钟
# rate-limit-order-detail-per-min: 600 # order/detail / 分钟
# rate-limit-max-retries: 3 # 命中「过于频繁」最多再试几次