旅游电商与酒店预订系统设计
1. 旅游电商平台架构
1.1 产品体系
旅游电商平台的产品体系覆盖用户出行全链路,主要包含以下品类。
| 品类 | 产品形态 | 供应链特征 | 库存特征 |
|---|---|---|---|
| 跟团游 | 固定团期、固定行程的打包产品 | 旅行社/地接社采购 | 按团期释放名额,售完即止 |
| 自由行 | 机+酒打包、可定制行程 | 动态打包,整合机票+酒店资源 | 依赖底层资源实时库存 |
| 机票 | 单程/往返/多程航班 | 航司GDS直连或中航信接口 | 实时查价,动态库存 |
| 酒店 | 间夜预订、钟点房 | PMS/批发商/直签 | 房价计划+库存日历 |
| 门票 | 景区门票、套票、年卡 | 景区系统直连/票务代理 | 每日限量,分时段入园 |
| 签证 | 单次/多次/加急办理 | 使领馆/签证中心/代办社 | 材料审核制,非实物库存 |
| 租车 | 自驾租车、接送机 | 租车公司/个人车主 | 按车辆/时间段锁定 |
产品表设计:
-- 产品主表
CREATE TABLE product (
product_id BIGINT PRIMARY KEY COMMENT '产品ID',
product_type TINYINT NOT NULL COMMENT '产品类型: 1-跟团游 2-自由行 3-机票 4-酒店 5-门票 6-签证 7-租车',
product_name VARCHAR(200) NOT NULL COMMENT '产品名称',
supplier_id BIGINT NOT NULL COMMENT '供应商ID',
category_id BIGINT NOT NULL COMMENT '类目ID',
sale_status TINYINT NOT NULL DEFAULT 0 COMMENT '销售状态: 0-下架 1-上架 2-售罄',
min_price BIGINT NOT NULL COMMENT '起售价(分)',
max_price BIGINT NOT NULL COMMENT '最高价(分)',
sale_city VARCHAR(50) COMMENT '出发城市',
dest_city VARCHAR(50) COMMENT '目的地城市',
valid_date_start DATE COMMENT '有效期起始',
valid_date_end DATE COMMENT '有效期结束',
audit_status TINYINT NOT NULL DEFAULT 0 COMMENT '审核状态: 0-待审核 1-通过 2-驳回',
create_time DATETIME NOT NULL,
update_time DATETIME NOT NULL,
INDEX idx_type_status (product_type, sale_status),
INDEX idx_supplier (supplier_id)
) COMMENT '产品主表';
-- 跟团游团期表
CREATE TABLE tour_group_schedule (
schedule_id BIGINT PRIMARY KEY COMMENT '团期ID',
product_id BIGINT NOT NULL COMMENT '产品ID',
depart_date DATE NOT NULL COMMENT '出发日期',
return_date DATE NOT NULL COMMENT '返程日期',
total_quota INT NOT NULL COMMENT '总名额',
sold_quota INT NOT NULL DEFAULT 0 COMMENT '已售名额',
price_adult BIGINT NOT NULL COMMENT '成人价(分)',
price_child BIGINT NOT NULL COMMENT '儿童价(分)',
room_diff BIGINT DEFAULT 0 COMMENT '单房差(分)',
sale_status TINYINT NOT NULL DEFAULT 0 COMMENT '销售状态: 0-可售 1-售罄 2-下线',
booking_deadline DATETIME COMMENT '最晚预订时间',
create_time DATETIME NOT NULL,
INDEX idx_product_depart (product_id, depart_date),
INDEX idx_depart_date (depart_date)
) COMMENT '跟团游团期表';1.2 订单体系
旅游电商订单的核心挑战在于多品类统一承载。一个订单可能同时包含机票、酒店、门票等多个品类的商品,且各品类的履约流程差异巨大。
订单模型设计原则:
- 主订单 + 子订单拆分:主订单记录用户、金额、支付等公共信息;每个品类独立子订单,分别维护各自的履约状态。
- 品类差异化状态机:酒店订单有"入住/离店"状态,机票有"出票/退改"状态,门票有"入园/核销"状态,各子订单独立流转。
- 统一支付、分账结算:主订单聚合支付,按子订单金额分账到不同供应商。
-- 旅游主订单表
CREATE TABLE travel_order (
order_id BIGINT PRIMARY KEY COMMENT '订单号',
user_id BIGINT NOT NULL COMMENT '用户ID',
order_status TINYINT NOT NULL COMMENT '订单状态: 0-待支付 1-已支付 2-履约中 3-已完成 4-已取消 5-退款中 6-已退款',
total_amount BIGINT NOT NULL COMMENT '总金额(分)',
pay_amount BIGINT NOT NULL COMMENT '实付金额(分)',
discount_amount BIGINT DEFAULT 0 COMMENT '优惠金额(分)',
pay_status TINYINT NOT NULL DEFAULT 0 COMMENT '支付状态: 0-未支付 1-已支付 2-已退款',
pay_method VARCHAR(20) COMMENT '支付方式: wechat/alipay/unionpay',
pay_time DATETIME COMMENT '支付时间',
contact_name VARCHAR(50) NOT NULL COMMENT '联系人姓名',
contact_phone VARCHAR(20) NOT NULL COMMENT '联系人手机',
contact_email VARCHAR(100) COMMENT '联系人邮箱',
remark VARCHAR(500) COMMENT '用户备注',
cancel_reason VARCHAR(200) COMMENT '取消原因',
cancel_time DATETIME COMMENT '取消时间',
create_time DATETIME NOT NULL,
update_time DATETIME NOT NULL,
INDEX idx_user_id (user_id),
INDEX idx_status (order_status),
INDEX idx_create_time (create_time)
) COMMENT '旅游主订单表';
-- 子订单表
CREATE TABLE travel_sub_order (
sub_order_id BIGINT PRIMARY KEY COMMENT '子订单ID',
order_id BIGINT NOT NULL COMMENT '主订单ID',
product_type TINYINT NOT NULL COMMENT '产品类型',
product_id BIGINT NOT NULL COMMENT '产品ID',
product_name VARCHAR(200) NOT NULL COMMENT '产品名称',
sku_id BIGINT COMMENT 'SKU ID(如房型ID/舱位ID)',
quantity INT NOT NULL DEFAULT 1 COMMENT '数量',
unit_price BIGINT NOT NULL COMMENT '单价(分)',
sub_amount BIGINT NOT NULL COMMENT '子订单金额(分)',
sub_status TINYINT NOT NULL COMMENT '子订单状态(见各品类状态机)',
supplier_id BIGINT NOT NULL COMMENT '供应商ID',
supplier_order_no VARCHAR(100) COMMENT '供应商侧订单号',
booking_status TINYINT NOT NULL DEFAULT 0 COMMENT '预订状态: 0-待确认 1-已确认 2-确认失败',
confirm_time DATETIME COMMENT '确认时间',
usage_start_time DATETIME COMMENT '使用开始时间(入住/入园/出发)',
usage_end_time DATETIME COMMENT '使用结束时间(离店/返程)',
cancel_policy_id BIGINT COMMENT '取消政策ID',
create_time DATETIME NOT NULL,
INDEX idx_order_id (order_id),
INDEX idx_product_type (product_type, sub_status),
INDEX idx_supplier (supplier_id)
) COMMENT '旅游子订单表';订单状态流转示例(酒店子订单):
待支付 ──→ 支付成功 ──→ 待确认 ──→ 已确认 ──→ 已入住 ──→ 已离店 ──→ 已完成
│ │ │
├──→ 已取消 ├──→ 预订失败(退款) ├──→ 取消(阶梯扣费)
│ │
└──→ 支付超时关闭 No Show(全额扣款)1.3 供应链体系
旅游供应链具有多层次、多环节的特征,供应商管理是平台的核心能力。
-- 供应商表
CREATE TABLE supplier (
supplier_id BIGINT PRIMARY KEY COMMENT '供应商ID',
supplier_name VARCHAR(200) NOT NULL COMMENT '供应商名称',
supplier_type TINYINT NOT NULL COMMENT '供应商类型: 1-直签 2-批发商 3-OTA分销 4-地接社',
contact_person VARCHAR(50) COMMENT '联系人',
contact_phone VARCHAR(20) COMMENT '联系电话',
cooperation_mode TINYINT NOT NULL COMMENT '合作模式: 1-底价采购 2-佣金模式 3-买断库存',
settle_cycle VARCHAR(20) COMMENT '结算周期: T+1/周结/月结',
credit_amount BIGINT DEFAULT 0 COMMENT '授信额度(分)',
status TINYINT NOT NULL DEFAULT 1 COMMENT '状态: 0-禁用 1-启用',
contract_start DATE COMMENT '合同起始日',
contract_end DATE COMMENT '合同结束日',
create_time DATETIME NOT NULL,
update_time DATETIME NOT NULL
) COMMENT '供应商表';
-- 产品采购表
CREATE TABLE product_procurement (
procurement_id BIGINT PRIMARY KEY COMMENT '采购ID',
product_id BIGINT NOT NULL COMMENT '产品ID',
supplier_id BIGINT NOT NULL COMMENT '供应商ID',
cost_price BIGINT NOT NULL COMMENT '采购底价(分)',
sale_price BIGINT NOT NULL COMMENT '建议售价(分)',
procurement_type TINYINT NOT NULL COMMENT '采购方式: 1-按需采购 2-定额采购 3-实时查询',
effective_date DATE NOT NULL COMMENT '生效日期',
expire_date DATE NOT NULL COMMENT '失效日期',
inventory_sync_mode TINYINT NOT NULL DEFAULT 1 COMMENT '库存同步模式: 1-实时同步 2-定时同步(分钟) 3-手动同步',
sync_interval INT COMMENT '同步间隔(分钟)',
create_time DATETIME NOT NULL,
INDEX idx_product (product_id),
INDEX idx_supplier_effective (supplier_id, effective_date, expire_date)
) COMMENT '产品采购表';
-- 结算单表
CREATE TABLE settle_bill (
settle_id BIGINT PRIMARY KEY COMMENT '结算单ID',
supplier_id BIGINT NOT NULL COMMENT '供应商ID',
settle_period VARCHAR(20) NOT NULL COMMENT '结算周期: 2026-07',
total_amount BIGINT NOT NULL COMMENT '结算总额(分)',
commission_amount BIGINT DEFAULT 0 COMMENT '佣金金额(分)',
settle_amount BIGINT NOT NULL COMMENT '实际结算金额(分)',
settle_status TINYINT NOT NULL DEFAULT 0 COMMENT '结算状态: 0-待结算 1-已对账 2-已确认 3-已付款',
paid_time DATETIME COMMENT '付款时间',
create_time DATETIME NOT NULL,
INDEX idx_supplier_period (supplier_id, settle_period)
) COMMENT '结算单表';库存同步策略:
实时同步(API直连)
┌─────────┐ HTTP/HTTPS ┌──────────┐
│ 平台库存 │ ◄────────────── │ 供应商系统 │
│ (缓存) │ QPS限流保护 │ (源库存) │
└─────────┘ └──────────┘
适用场景: 酒店/机票等实时性要求高的品类
定时同步(批量拉取)
┌─────────┐ 每5-15分钟拉取 ┌──────────┐
│ 平台库存 │ ◄────────────── │ 供应商系统 │
│ (本地表) │ │ (FTP/DB) │
└─────────┘ └──────────┘
适用场景: 跟团游团期/门票等变化频率低的品类
手动同步(运营操作)
运营后台 → 点击"同步库存" → 触发全量/增量同步
适用场景: 特殊活动/价格调整后的手动校准2. 酒店预订系统
2.1 酒店资源管理
酒店资源管理模块维护酒店的基础信息、房型定义、房价计划和图片设施等数据,是预订系统的基础数据源。
-- 酒店信息表
CREATE TABLE hotel_info (
hotel_id BIGINT PRIMARY KEY COMMENT '酒店ID',
hotel_name VARCHAR(200) NOT NULL COMMENT '酒店名称',
hotel_name_en VARCHAR(200) COMMENT '英文名称',
brand_id BIGINT COMMENT '品牌ID',
brand_name VARCHAR(100) COMMENT '品牌名称',
star_rating TINYINT COMMENT '星级: 1-5星',
hotel_rating DECIMAL(2,1) COMMENT '平台评分: 1.0-5.0',
address VARCHAR(300) NOT NULL COMMENT '详细地址',
province VARCHAR(50) COMMENT '省份',
city VARCHAR(50) NOT NULL COMMENT '城市',
district VARCHAR(50) COMMENT '区县',
longitude DECIMAL(10,6) COMMENT '经度',
latitude DECIMAL(10,6) COMMENT '纬度',
description TEXT COMMENT '酒店简介',
phone VARCHAR(20) COMMENT '前台电话',
check_in_time VARCHAR(10) DEFAULT '14:00' COMMENT '入住时间',
check_out_time VARCHAR(10) DEFAULT '12:00' COMMENT '离店时间',
pet_policy TINYINT DEFAULT 0 COMMENT '宠物政策: 0-不允许 1-允许 2-部分房型允许',
status TINYINT NOT NULL DEFAULT 1 COMMENT '状态: 0-下线 1-上线',
create_time DATETIME NOT NULL,
update_time DATETIME NOT NULL,
INDEX idx_city (city),
INDEX idx_brand (brand_id),
INDEX idx_star (star_rating),
INDEX idx_location (province, city, district)
) COMMENT '酒店信息表';
-- 房型表
CREATE TABLE hotel_room_type (
room_type_id BIGINT PRIMARY KEY COMMENT '房型ID',
hotel_id BIGINT NOT NULL COMMENT '酒店ID',
room_name VARCHAR(100) NOT NULL COMMENT '房型名称: 高级大床房/豪华双床房',
room_name_en VARCHAR(100) COMMENT '英文房型名称',
bed_type TINYINT NOT NULL COMMENT '床型: 1-大床 2-双床 3-多床 4-高低床',
max_occupancy INT NOT NULL COMMENT '最大入住人数',
floor_area INT COMMENT '房间面积(平方米)',
floor_level VARCHAR(50) COMMENT '所在楼层: 5-10层',
window_type TINYINT DEFAULT 0 COMMENT '窗户类型: 0-无窗 1-有窗 2-落地窗',
smoking_policy TINYINT DEFAULT 0 COMMENT '吸烟政策: 0-禁烟 1-可吸烟',
quantity INT NOT NULL DEFAULT 10 COMMENT '房间数量(同房型总间数)',
description TEXT COMMENT '房型描述',
create_time DATETIME NOT NULL,
INDEX idx_hotel (hotel_id),
INDEX idx_bed_type (bed_type)
) COMMENT '酒店房型表';
-- 酒店图片表
CREATE TABLE hotel_image (
image_id BIGINT PRIMARY KEY COMMENT '图片ID',
hotel_id BIGINT NOT NULL COMMENT '酒店ID',
room_type_id BIGINT COMMENT '房型ID(为空则为酒店公共图片)',
image_type TINYINT NOT NULL COMMENT '图片类型: 1-外景 2-大堂 3-客房 4-餐厅 5-设施 6-周边',
image_url VARCHAR(500) NOT NULL COMMENT '图片URL',
sort_order INT DEFAULT 0 COMMENT '排序',
is_cover TINYINT DEFAULT 0 COMMENT '是否封面: 0-否 1-是',
upload_time DATETIME NOT NULL,
INDEX idx_hotel_type (hotel_id, image_type)
) COMMENT '酒店图片表';
-- 酒店设施表
CREATE TABLE hotel_facility (
facility_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '设施ID',
hotel_id BIGINT NOT NULL COMMENT '酒店ID',
facility_code VARCHAR(50) NOT NULL COMMENT '设施编码: wifi/parking/restaurant/gym/pool/spa',
facility_name VARCHAR(100) NOT NULL COMMENT '设施名称',
facility_type TINYINT NOT NULL COMMENT '设施类型: 1-通用设施 2-客房设施 3-餐饮设施 4-康乐设施',
is_free TINYINT DEFAULT 1 COMMENT '是否免费: 0-收费 1-免费',
open_time VARCHAR(50) COMMENT '营业时间: 06:00-22:00',
description VARCHAR(200) COMMENT '设施说明',
INDEX idx_hotel (hotel_id),
INDEX idx_code (facility_code)
) COMMENT '酒店设施表';2.2 房价库存核心引擎
房价库存引擎是酒店预订系统的核心,它决定了"某天某个房型是否有房、卖多少钱"。核心概念是 RatePlan(房价计划)+ Inventory(库存) 的组合管理。
2.2.1 房价计划设计
房价计划(RatePlan)定义了特定条件下房型的定价规则。一个房型可以绑定多个房价计划,每个房价计划可以包含多种优惠策略。
-- 房价计划主表
CREATE TABLE rate_plan (
rate_plan_id BIGINT PRIMARY KEY COMMENT '房价计划ID',
hotel_id BIGINT NOT NULL COMMENT '酒店ID',
room_type_id BIGINT NOT NULL COMMENT '房型ID',
plan_name VARCHAR(100) NOT NULL COMMENT '计划名称: 标准价/含早价/连住优惠/早订优惠/Last Minute',
plan_type TINYINT NOT NULL COMMENT '计划类型: 1-标准价 2-含早价 3-连住优惠 4-早订优惠 5-Last Minute 6-协议价',
sellable_channel VARCHAR(100) COMMENT '可售渠道: all/ota/website/wholesale(逗号分隔多选)',
is_active TINYINT DEFAULT 1 COMMENT '是否启用: 0-禁用 1-启用',
priority INT DEFAULT 0 COMMENT '优先级(数字越大越优先)',
advance_days_min INT COMMENT '提前预订最小天数(早订优惠)',
advance_days_max INT COMMENT '提前预订最大天数(早订优惠)',
min_stay INT DEFAULT 1 COMMENT '最小入住天数(连住优惠)',
effective_date DATE NOT NULL COMMENT '生效日期',
expire_date DATE NOT NULL COMMENT '失效日期',
create_time DATETIME NOT NULL,
INDEX idx_hotel_room (hotel_id, room_type_id),
INDEX idx_plan_type (plan_type, is_active),
INDEX idx_effective (effective_date, expire_date)
) COMMENT '房价计划主表';
-- 房价日历表(按日期维度存储)
CREATE TABLE rate_calendar (
rate_calendar_id BIGINT PRIMARY KEY COMMENT '房价日历ID',
rate_plan_id BIGINT NOT NULL COMMENT '房价计划ID',
hotel_id BIGINT NOT NULL COMMENT '酒店ID',
room_type_id BIGINT NOT NULL COMMENT '房型ID',
date DATE NOT NULL COMMENT '日期',
room_price BIGINT NOT NULL COMMENT '单间房价(分)',
breakfast_included TINYINT DEFAULT 0 COMMENT '是否含早: 0-不含 1-含早',
currency VARCHAR(10) DEFAULT 'CNY' COMMENT '币种',
is_holiday TINYINT DEFAULT 0 COMMENT '是否节假日: 0-否 1-是',
is_weekend TINYINT DEFAULT 0 COMMENT '是否周末: 0-否 1-是',
create_time DATETIME NOT NULL,
update_time DATETIME NOT NULL,
UNIQUE KEY uk_plan_date (rate_plan_id, date),
INDEX idx_hotel_date (hotel_id, date),
INDEX idx_room_date (room_type_id, date)
) COMMENT '房价日历表';
-- 库存日历表
CREATE TABLE inventory_calendar (
inventory_id BIGINT PRIMARY KEY COMMENT '库存ID',
hotel_id BIGINT NOT NULL COMMENT '酒店ID',
room_type_id BIGINT NOT NULL COMMENT '房型ID',
date DATE NOT NULL COMMENT '日期',
total_inventory INT NOT NULL COMMENT '总库存(房间数)',
available_inventory INT NOT NULL COMMENT '可用库存',
booked_inventory INT DEFAULT 0 COMMENT '已预订库存',
maintenance_inventory INT DEFAULT 0 COMMENT '维修房数量',
blocked_inventory INT DEFAULT 0 COMMENT '保留房/保护房数量',
room_status TINYINT NOT NULL DEFAULT 0 COMMENT '房态: 0-可售 1-维修 2-保留 3-关房',
overbooking_limit INT DEFAULT 0 COMMENT '超售限额(允许超售的房间数)',
min_stay INT DEFAULT 1 COMMENT '最小入住天数限制',
max_stay INT DEFAULT 30 COMMENT '最大入住天数限制',
arrival_allowed TINYINT DEFAULT 1 COMMENT '是否允许入住: 0-禁止入住 1-允许',
departure_allowed TINYINT DEFAULT 1 COMMENT '是否允许离店: 0-禁止离店 1-允许',
create_time DATETIME NOT NULL,
update_time DATETIME NOT NULL,
UNIQUE KEY uk_room_date (room_type_id, date),
INDEX idx_hotel_date (hotel_id, date),
INDEX idx_status (room_status)
) COMMENT '库存日历表';2.2.2 价格规则引擎
房价规则引擎支持多种定价策略的组合应用。
连住优惠(Long Stay Discount):
-- 连住优惠规则表
CREATE TABLE long_stay_rule (
rule_id BIGINT PRIMARY KEY COMMENT '规则ID',
rate_plan_id BIGINT NOT NULL COMMENT '房价计划ID',
min_nights INT NOT NULL COMMENT '最少连住天数',
max_nights INT COMMENT '最多连住天数(空表示不限制)',
discount_type TINYINT NOT NULL COMMENT '优惠类型: 1-百分比折扣 2-固定金额减免 3-最后一晚免费',
discount_value BIGINT NOT NULL COMMENT '优惠值: 百分比(如20表示8折)/减免金额(分)',
stackable TINYINT DEFAULT 0 COMMENT '是否可叠加其他优惠: 0-不可叠加 1-可叠加',
INDEX idx_rate_plan (rate_plan_id)
) COMMENT '连住优惠规则表';
-- 早订优惠规则表
CREATE TABLE early_booking_rule (
rule_id BIGINT PRIMARY KEY COMMENT '规则ID',
rate_plan_id BIGINT NOT NULL COMMENT '房价计划ID',
advance_days_min INT NOT NULL COMMENT '提前预订最小天数',
advance_days_max INT NOT NULL COMMENT '提前预订最大天数',
discount_type TINYINT NOT NULL COMMENT '优惠类型: 1-百分比折扣 2-固定金额减免',
discount_value BIGINT NOT NULL COMMENT '优惠值',
INDEX idx_rate_plan (rate_plan_id)
) COMMENT '早订优惠规则表';
-- Last Minute 规则表
CREATE TABLE last_minute_rule (
rule_id BIGINT PRIMARY KEY COMMENT '规则ID',
rate_plan_id BIGINT NOT NULL COMMENT '房价计划ID',
before_checkin INT NOT NULL COMMENT '距入住小时数',
discount_type TINYINT NOT NULL COMMENT '优惠类型: 1-百分比折扣 2-固定金额减免',
discount_value BIGINT NOT NULL COMMENT '优惠值',
INDEX idx_rate_plan (rate_plan_id)
) COMMENT 'Last Minute 规则表';价格计算逻辑示例:
public class PriceCalculator {
/**
* 计算入住期间的总房价
*
* @param ratePlanId 房价计划ID
* @param checkInDate 入住日期
* @param checkOutDate 离店日期
* @return 总价(分)
*/
public long calculateTotalPrice(Long ratePlanId, LocalDate checkInDate, LocalDate checkOutDate) {
long totalPrice = 0;
LocalDate currentDate = checkInDate;
int nights = 0;
// Step 1: 按天累加基础房价
while (currentDate.isBefore(checkOutDate)) {
Long dailyPrice = rateCalendarMapper.getPrice(ratePlanId, currentDate);
if (dailyPrice == null) {
throw new BusinessException("日期 " + currentDate + " 无可用房价");
}
totalPrice += dailyPrice;
currentDate = currentDate.plusDays(1);
nights++;
}
// Step 2: 应用连住优惠
LongStayRule longStayRule = longStayRuleMapper.findByRatePlan(ratePlanId);
if (longStayRule != null && nights >= longStayRule.getMinNights()) {
totalPrice = applyDiscount(totalPrice, longStayRule.getDiscountType(),
longStayRule.getDiscountValue());
}
// Step 3: 应用早订优惠(计算提前预订天数)
long advanceDays = ChronoUnit.DAYS.between(LocalDate.now(), checkInDate);
EarlyBookingRule earlyRule = earlyBookingRuleMapper.findByRatePlan(ratePlanId);
if (earlyRule != null && advanceDays >= earlyRule.getAdvanceDaysMin()
&& advanceDays <= earlyRule.getAdvanceDaysMax()) {
totalPrice = applyDiscount(totalPrice, earlyRule.getDiscountType(),
earlyRule.getDiscountValue());
}
return totalPrice;
}
private long applyDiscount(long price, int discountType, long value) {
switch (discountType) {
case 1: // 百分比折扣
return price * (100 - value) / 100;
case 2: // 固定金额减免
return Math.max(price - value, 0);
case 3: // 最后一晚免费
// 在Step 1中已在循环外层处理
return price;
default:
return price;
}
}
}2.3 搜索系统
酒店搜索需要支持多维度筛选和实时库存校验。
-- 酒店搜索索引表(Elasticsearch同步结构)
CREATE TABLE hotel_search_index (
hotel_id BIGINT PRIMARY KEY COMMENT '酒店ID',
hotel_name VARCHAR(200) NOT NULL COMMENT '酒店名称',
city VARCHAR(50) NOT NULL COMMENT '城市',
district VARCHAR(50) COMMENT '区县',
zone_id INT COMMENT '商圈ID',
star_rating TINYINT COMMENT '星级',
hotel_rating DECIMAL(2,1) COMMENT '评分',
total_reviews INT DEFAULT 0 COMMENT '评论总数',
min_price BIGINT COMMENT '当前最低价(分)',
facilities JSON COMMENT '设施列表: ["wifi","parking","pool"]',
room_type_ids JSON COMMENT '房型ID列表',
latitude DECIMAL(10,6) COMMENT '纬度',
longitude DECIMAL(10,6) COMMENT '经度',
geo_hash VARCHAR(20) COMMENT '地理哈希值',
sale_status TINYINT NOT NULL COMMENT '销售状态',
update_time DATETIME NOT NULL,
INDEX idx_city_price (city, min_price),
INDEX idx_star_rating (star_rating),
INDEX idx_geo (geo_hash),
INDEX idx_district (city, district)
) COMMENT '酒店搜索索引表';搜索逻辑(面向搜索服务):
搜索请求:
{
"city": "北京",
"check_in": "2026-08-01",
"check_out": "2026-08-03",
"guests": 2,
"rooms": 1,
"filters": {
"price_min": 20000, // 200元
"price_max": 80000, // 800元
"star_ratings": [4, 5],
"facilities": ["wifi", "breakfast"],
"district": "朝阳区",
"sort_by": "price_asc" // 按价格升序
}
}
搜索流程:
1. 基础过滤: city + check_in日期仍有可用库存的酒店
2. 多维筛选: 星级/价格区间/设施/行政区
3. 库存校验: 查询 inventory_calendar 表确保在住期间每天都有可用库存
4. 价格计算: 调用 PriceCalculator 获取入住期间总价
5. 排序分页: 按价格/评分/距离等排序2.4 预订流程
酒店预订标准流程分为六个阶段:搜索 -> 详情 -> 下单 -> 支付 -> 确认 -> 入住 -> 离店。
预订流程状态图:
搜索酒店
│
▼
查看详情(房型/价格/设施/评价)
│
▼
选择房型+RationPlan → 填写入住人信息
│
▼
创建订单(锁定库存 + 生成订单)
│
▼
支付流程
├── 即时确认: 支付成功 → 自动确认 → 发送确认函
│
├── 等待确认: 支付成功 → 人工/系统确认 → 确认结果通知
│ 确认失败 → 全额退款 + 补偿
│
▼
入住办理
│
├── 前台出示证件 → 办理入住 → 制房卡
│
├── 自助入住(部分酒店) → 扫码验证 → 自助取房卡
│
▼
在住期间(服务/加床/延住)
│
▼
办理离店
├── 查房 → 结算杂费 → 开具发票 → 完成
├── 免查房(部分酒店) → 快速离店 → 后续扣款库存锁定策略:
public class InventoryLockService {
/**
* 下单时锁定库存
* 使用 Redis 分布式锁 + 数据库乐观锁防止超卖
*/
@Transactional
public boolean lockInventory(BookingRequest request) {
String lockKey = "hotel:inventory:" + request.getHotelId()
+ ":" + request.getRoomTypeId();
// Redis 分布式锁,防止并发下单
boolean locked = redisLock.tryLock(lockKey, 5, TimeUnit.SECONDS);
if (!locked) {
throw new BusinessException("系统繁忙,请稍后重试");
}
try {
LocalDate date = request.getCheckInDate();
while (date.isBefore(request.getCheckOutDate())) {
// 数据库乐观锁更新: update ... where available_inventory > 0
int affected = inventoryCalendarMapper.decreaseInventory(
request.getRoomTypeId(), date, 1);
if (affected == 0) {
// 库存不足,回滚已锁定的日期
rollbackInventory(request, date);
throw new BusinessException("库存不足");
}
date = date.plusDays(1);
}
return true;
} finally {
redisLock.unlock(lockKey);
}
}
}3. 房态管理
3.1 房态类型定义
房态管理(Room Status Management)是指对酒店每个房间在不同日期的状态进行精细化管控。
| 房态 | 英文 | 含义 | 对销售影响 |
|---|---|---|---|
| 可售房 | Available | 房间正常可预订销售 | 正常售卖 |
| 维修房 | Out of Order (OOO) | 房间设施故障/翻新中 | 不可售卖,不计入可用库存 |
| 保留房 | Out of Service (OOS) | 留作自用/员工/紧急备用 | 不可售卖,但硬件可用 |
| 关房 | Closed to Arrival (CTA) / Closed to Departure (CTD) | 限制入住/离店 | CTA: 当天不可入住; CTD: 当天不可离店 |
| 保护房 | Protected | 为特定渠道/会员预留 | 仅特定渠道可售 |
-- 房态明细表(按房间号+日期维度)
CREATE TABLE room_status_detail (
status_id BIGINT PRIMARY KEY COMMENT '房态ID',
hotel_id BIGINT NOT NULL COMMENT '酒店ID',
room_type_id BIGINT NOT NULL COMMENT '房型ID',
room_number VARCHAR(20) NOT NULL COMMENT '房间号: 1201/1202',
date DATE NOT NULL COMMENT '日期',
room_status TINYINT NOT NULL COMMENT '房态: 0-可售 1-维修 2-保留 3-关房(CTA) 4-关房(CTD) 5-保护房',
reason VARCHAR(200) COMMENT '原因说明',
operator VARCHAR(50) COMMENT '操作人',
create_time DATETIME NOT NULL,
update_time DATETIME NOT NULL,
UNIQUE KEY uk_room_date (room_number, date),
INDEX idx_hotel_date (hotel_id, date),
INDEX idx_status (room_status)
) COMMENT '房态明细表';3.2 超售控制与保护房
超售(Overbooking)是酒店行业的常见做法,基于 No Show 和取消率的历史数据,有策略地超额销售。
超售算法逻辑:
public class OverbookingManager {
/**
* 计算每日可超售数量
* 基于历史取消率 + No Show率 + 节假日因子
*/
public int calculateOverbookingLimit(Long hotelId, Long roomTypeId, LocalDate date) {
// 1. 获取历史数据
double historicalCancelRate = statisticsMapper.getCancelRate(hotelId, roomTypeId, date);
double historicalNoShowRate = statisticsMapper.getNoShowRate(hotelId, roomTypeId, date);
// 2. 获取总库存
InventoryCalendar inventory = inventoryCalendarMapper.findByRoomAndDate(
roomTypeId, date);
// 3. 计算预期取消量
int bookedCount = inventory.getBookedInventory();
int expectedCancellations = (int) Math.round(
bookedCount * (historicalCancelRate + historicalNoShowRate));
// 4. 确定最终超售限额
int baseOverbooking = expectedCancellations;
int holidayFactor = getHolidayFactor(date); // 节假日收缩超售比例
return Math.min(baseOverbooking * holidayFactor / 100, inventory.getOverbookingLimit());
}
private int getHolidayFactor(LocalDate date) {
// 节假日超售比例降低50%,避免到店无房风险
if (isHoliday(date)) return 50;
// 周末降低20%
if (isWeekend(date)) return 80;
return 100;
}
}保护房(Protected Inventory)管理:
保护房是预留给特定渠道或会员等级的库存,不对外开放销售。
CREATE TABLE protected_inventory (
protect_id BIGINT PRIMARY KEY COMMENT '保护ID',
hotel_id BIGINT NOT NULL COMMENT '酒店ID',
room_type_id BIGINT NOT NULL COMMENT '房型ID',
date DATE NOT NULL COMMENT '日期',
protect_count INT NOT NULL COMMENT '保护房数量',
channel_code VARCHAR(50) COMMENT '预留渠道: vip/wholesale/corporate(空=通用保护)',
release_time DATETIME COMMENT '释放时间(到时间未售出则释放回公共库存)',
is_released TINYINT DEFAULT 0 COMMENT '是否已释放: 0-未释放 1-已释放',
create_time DATETIME NOT NULL,
INDEX idx_hotel_date (hotel_id, date),
INDEX idx_channel (channel_code)
) COMMENT '保护房表';3.3 房态日历与运营指标
房态日历是酒店运营的核心管理界面,通常以日历形式展示以下关键指标:
| 指标 | 全称 | 计算公式 | 含义 |
|---|---|---|---|
| OCC | Occupancy Rate | 已售间夜数 / 可售间夜数 x 100% | 入住率,反映房间利用效率 |
| ADR | Average Daily Rate | 客房总收入 / 已售间夜数 | 平均房价,反映定价水平 |
| RevPAR | Revenue Per Available Room | ADR x OCC 或 客房总收入 / 可售间夜数 | 每间可售房收入,核心收益指标 |
| GOPPAR | Gross Operating Profit Per Available Room | 经营毛利 / 可售间夜数 | 每间可售房经营利润(含非客房收入) |
房态日历数据服务示例:
public class RoomStatusCalendarService {
/**
* 获取某酒店某月的房态日历
*/
public RoomStatusCalendarVO getMonthlyCalendar(Long hotelId, int year, int month) {
// 获取该月每天的可售房数、已售房数、平均房价
List<DailyRoomStats> statsList = inventoryCalendarMapper.getMonthlyStats(
hotelId, year, month);
RoomStatusCalendarVO calendar = new RoomStatusCalendarVO();
calendar.setHotelId(hotelId);
calendar.setYear(year);
calendar.setMonth(month);
long totalAvailableRoomNights = 0;
long totalSoldRoomNights = 0;
long totalRevenue = 0;
for (DailyRoomStats stats : statsList) {
DailyCalendarVO day = new DailyCalendarVO();
day.setDate(stats.getDate());
day.setAvailableRooms(stats.getTotalInventory());
day.setSoldRooms(stats.getBookedInventory());
// 计算单日指标
int available = stats.getTotalInventory();
int sold = stats.getBookedInventory();
BigDecimal price = stats.getAvgPrice();
day.setOccupancyRate(sold * 100.0 / available);
day.setAdr(price);
day.setRevPAR(price.multiply(BigDecimal.valueOf(sold))
.divide(BigDecimal.valueOf(available), 2, RoundingMode.HALF_UP));
calendar.getDailyData().add(day);
totalAvailableRoomNights += available;
totalSoldRoomNights += sold;
totalRevenue += price.longValue() * sold;
}
// 计算月度汇总指标
if (totalAvailableRoomNights > 0) {
calendar.setMonthOccupancy(
totalSoldRoomNights * 100.0 / totalAvailableRoomNights);
calendar.setMonthAdr(
totalRevenue / (double) totalSoldRoomNights);
calendar.setMonthRevPAR(
totalRevenue / (double) totalAvailableRoomNights);
}
return calendar;
}
}4. 订单确认与取消政策
4.1 订单确认模式
酒店订单确认分为即时确认和等待确认两种模式,取决于供应商的对接方式。
-- 订单确认配置表
CREATE TABLE booking_confirmation_config (
config_id BIGINT PRIMARY KEY COMMENT '配置ID',
hotel_id BIGINT NOT NULL COMMENT '酒店ID',
room_type_id BIGINT COMMENT '房型ID(空=全部房型)',
rate_plan_id BIGINT COMMENT '房价计划ID(空=全部计划)',
confirm_mode TINYINT NOT NULL COMMENT '确认模式: 1-即时确认 2-等待确认 3-自动确认(XX分钟后)',
auto_confirm_minutes INT COMMENT '自动确认等待分钟数',
max_confirm_hours INT COMMENT '最晚确认小时数(超时自动取消)',
need_manual_review TINYINT DEFAULT 0 COMMENT '是否需要人工审核: 0-否 1-是',
effective_date DATE NOT NULL COMMENT '生效日期',
expire_date DATE NOT NULL COMMENT '失效日期',
INDEX idx_hotel (hotel_id)
) COMMENT '订单确认配置表';即时确认流程(API直连酒店PMS):
用户下单 → 平台生成订单 → 调用供应商API
→ 供应商返回确认号(200 OK)
→ 平台标记"已确认"
→ 发送确认邮件/短信
→ 完成
典型响应时间: 1-3秒等待确认流程(人工/批发商模式):
用户下单 → 平台生成订单(待确认)
→ 通知供应商/运营
→ 供应商确认有房 → 平台标记"已确认" → 通知用户
→ 或供应商确认无房 → 全额退款 + 补偿 → 通知用户
典型确认时间: 15分钟-2小时4.2 取消规则阶梯
取消规则(Cancellation Policy)定义用户在不同时间点取消订单的扣费规则,通常按阶梯设置。
-- 取消规则表
CREATE TABLE cancel_policy (
policy_id BIGINT PRIMARY KEY COMMENT '政策ID',
policy_name VARCHAR(100) NOT NULL COMMENT '政策名称: 宽松/标准/严格/不可取消',
hotel_id BIGINT NOT NULL COMMENT '酒店ID',
room_type_id BIGINT COMMENT '房型ID(空=全部)',
rate_plan_id BIGINT COMMENT '房价计划ID',
effective_date DATE NOT NULL COMMENT '生效日期',
expire_date DATE NOT NULL COMMENT '失效日期',
create_time DATETIME NOT NULL,
INDEX idx_hotel (hotel_id),
INDEX idx_rate_plan (rate_plan_id)
) COMMENT '取消规则表';
-- 取消规则阶梯明细表
CREATE TABLE cancel_policy_tier (
tier_id BIGINT PRIMARY KEY COMMENT '阶梯ID',
policy_id BIGINT NOT NULL COMMENT '取消规则ID',
tier_order INT NOT NULL COMMENT '阶梯顺序(1开始)',
cancel_before_checkin_hours INT COMMENT '距入住前N小时(空=不限制)',
cancel_before_checkin_days INT COMMENT '距入住前N天(空=不限制)',
deduct_type TINYINT NOT NULL COMMENT '扣费类型: 1-全额退款 2-扣百分比 3-扣固定金额 4-扣首晚房费 5-不可退款',
deduct_value BIGINT COMMENT '扣费值(百分比数字/固定金额)',
no_show_deduct TINYINT COMMENT 'No Show扣费类型(相同定义)',
no_show_value BIGINT COMMENT 'No Show扣费值',
INDEX idx_policy (policy_id)
) COMMENT '取消规则阶梯明细表';常见取消规则阶梯示例:
宽松政策(适合普通客房):
Tier 1: 入住前24小时前取消 → 全额退款
Tier 2: 入住前24小时内取消 → 扣首晚房费
Tier 3: No Show → 全额扣款
标准政策(适合节假日):
Tier 1: 入住前7天前取消 → 全额退款
Tier 2: 入住前3-7天取消 → 扣50%房费
Tier 3: 入住前3天内取消 → 扣首晚房费
Tier 4: No Show → 全额扣款
严格政策(适合特价/促销):
Tier 1: 入住前30天前取消 → 全额退款
Tier 2: 入住前7-30天取消 → 扣50%房费
Tier 3: 入住前7天内取消 → 不可取消/全额扣款
Tier 4: No Show → 全额扣款
不可取消(适合Last Minute特价):
Tier 1: 预订即不可取消 → 全额扣款
Tier 2: No Show → 全额扣款取消扣费计算逻辑:
public class CancellationCalculator {
/**
* 计算取消应扣金额
*
* @param order 订单信息
* @param cancelTime 取消时间
* @return 应退金额(分)
*/
public RefundResult calculateRefund(TravelSubOrder order, LocalDateTime cancelTime) {
CancelPolicy policy = cancelPolicyMapper.findById(order.getCancelPolicyId());
if (policy == null) {
return new RefundResult(order.getSubAmount(), 0, "默认全额退款");
}
List<CancelPolicyTier> tiers = cancelPolicyTierMapper.findByPolicyId(
policy.getPolicyId());
LocalDateTime checkInTime = order.getUsageStartTime();
long hoursBeforeCheckin = ChronoUnit.HOURS.between(cancelTime, checkInTime);
// 判断是否为 No Show(已过入住时间未入住)
if (cancelTime.isAfter(checkInTime)) {
CancelPolicyTier noShowTier = tiers.stream()
.filter(t -> t.getNoShowDeduct() != null)
.findFirst().orElse(null);
if (noShowTier != null) {
return calculateDeduction(order.getSubAmount(),
noShowTier.getNoShowDeduct(), noShowTier.getNoShowValue());
}
}
// 按阶梯匹配
for (CancelPolicyTier tier : tiers) {
int daysLimit = tier.getCancelBeforeCheckinDays();
int hoursLimit = tier.getCancelBeforeCheckinHours();
boolean matchesDay = (daysLimit == 0)
|| (hoursBeforeCheckin >= daysLimit * 24L);
boolean matchesHour = (hoursLimit == 0)
|| (hoursBeforeCheckin >= hoursLimit);
if (matchesDay && matchesHour) {
return calculateDeduction(order.getSubAmount(),
tier.getDeductType(), tier.getDeductValue());
}
}
// 默认全额退款
return new RefundResult(order.getSubAmount(), 0, "全额退款");
}
private RefundResult calculateDeduction(long totalAmount, int deductType, long deductValue) {
long deductAmount;
switch (deductType) {
case 1: // 全额退款
deductAmount = 0;
break;
case 2: // 扣百分比
deductAmount = totalAmount * deductValue / 100;
break;
case 3: // 扣固定金额
deductAmount = Math.min(deductValue, totalAmount);
break;
case 4: // 扣首晚房费
deductAmount = Math.min(deductValue, totalAmount);
break;
case 5: // 不可退款
deductAmount = totalAmount;
break;
default:
deductAmount = 0;
}
return new RefundResult(totalAmount, deductAmount,
"扣除 " + deductAmount + " 分,退款 " + (totalAmount - deductAmount) + " 分");
}
}4.3 担保预订与付款方式
| 付款类型 | 说明 | 扣款时机 | 适用场景 |
|---|---|---|---|
| 担保预订 | 仅提供信用卡担保,不实际扣款 | No Show时扣款 | 信用良好的用户/会员 |
| 预付 | 下单时全额支付 | 下单时 | 特价房/促销/节假日 |
| 现付 | 到店前台支付 | 入住时 | 标准房价/商务协议价 |
| 部分预付 | 支付定金+到店付尾款 | 下单(定金)+入住(尾款) | 长住/高端酒店 |
-- 付款方式配置表
CREATE TABLE payment_config (
config_id BIGINT PRIMARY KEY COMMENT '配置ID',
hotel_id BIGINT NOT NULL COMMENT '酒店ID',
room_type_id BIGINT COMMENT '房型ID',
rate_plan_id BIGINT COMMENT '房价计划ID',
payment_type TINYINT NOT NULL COMMENT '付款类型: 1-担保预订 2-预付 3-现付 4-部分预付',
deposit_ratio INT COMMENT '预付比例(百分比,部分预付时使用)',
deposit_amount BIGINT COMMENT '预付固定金额(分)',
guarantee_required TINYINT DEFAULT 1 COMMENT '是否需要担保: 0-否 1-是',
INDEX idx_hotel (hotel_id),
INDEX idx_rate_plan (rate_plan_id)
) COMMENT '付款方式配置表';5. 多渠道库存分发
5.1 渠道管理
酒店资源通常需要通过多个渠道进行分销,包括自有官网、OTA平台、批发商等。
-- 销售渠道表
CREATE TABLE sales_channel (
channel_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '渠道ID',
channel_code VARCHAR(50) NOT NULL UNIQUE COMMENT '渠道编码: ctrip/meituan/fliggy/booking/expedia/wholesale/website',
channel_name VARCHAR(100) NOT NULL COMMENT '渠道名称',
channel_type TINYINT NOT NULL COMMENT '渠道类型: 1-OTA直连 2-批发商 3-自有官网 4-GDS',
connect_mode TINYINT NOT NULL COMMENT '对接方式: 1-API直连 2-EDI文件 3-人工维护 4-分销平台',
commission_rate INT DEFAULT 0 COMMENT '佣金比例(百分比)',
settle_cycle VARCHAR(20) COMMENT '结算周期',
is_active TINYINT DEFAULT 1 COMMENT '是否启用',
create_time DATETIME NOT NULL,
update_time DATETIME NOT NULL
) COMMENT '销售渠道表';
-- 酒店渠道关系表
CREATE TABLE hotel_channel_mapping (
mapping_id BIGINT PRIMARY KEY COMMENT '映射ID',
hotel_id BIGINT NOT NULL COMMENT '酒店ID',
channel_id INT NOT NULL COMMENT '渠道ID',
channel_hotel_id VARCHAR(100) COMMENT '渠道侧酒店ID(如携程酒店ID)',
inventory_mode TINYINT NOT NULL COMMENT '库存模式: 1-配额分配 2-实时库存 3-阈值模式',
allocated_rooms INT COMMENT '配额分配数',
threshold_value INT COMMENT '阈值(剩余N间时停止销售)',
price_mode TINYINT NOT NULL COMMENT '价格模式: 1-统一售价 2-渠道加价 3-渠道自主定价',
price_adjust BIGINT COMMENT '价格调整值(分,正数加价/负数降价)',
is_sync_enabled TINYINT DEFAULT 1 COMMENT '是否启用同步',
sync_interval INT DEFAULT 5 COMMENT '同步间隔(分钟)',
create_time DATETIME NOT NULL,
UNIQUE KEY uk_hotel_channel (hotel_id, channel_id),
INDEX idx_channel (channel_id)
) COMMENT '酒店渠道关系表';5.2 库存分配模式
多渠道分发时,库存分配有三种常见模式。
配额分配(Allocation Mode):
将固定数量的房间分配给每个渠道,各渠道独立消耗自己的配额,互不影响。
┌──────────────────────┐
│ 总库存: 100间/天 │
└──────────┬───────────┘
│
┌────────────────────┼────────────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 携程: 30间 │ │ 美团: 20间 │ │ 官网: 50间 │
│ 已售: 12间 │ │ 已售: 8间 │ │ 已售: 30间 │
│ 剩余: 18间 │ │ 剩余: 12间 │ │ 剩余: 20间 │
└──────────────┘ └──────────────┘ └──────────────┘
优点: 各渠道独立,一个渠道售罄不影响其他渠道
缺点: 可能某渠道配额用完而其他渠道仍有空余,造成整体浪费实时库存(Real-time Inventory):
所有渠道共享同一库存池,先到先得。
┌──────────────────────┐
│ 共享库存池: 100间/天 │
│ 已售: 80间 │
│ 可用: 20间 │
└──────────────────────┘
▲ ▲ ▲
│ │ │
┌──────┘ │ └──────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 携程 │ │ 美团 │ │ 官网 │
└──────────┘ └──────────┘ └──────────┘
优点: 库存利用率最高
缺点: 需要实时同步,并发控制复杂度高,可能出现超卖阈值模式(Threshold Mode):
共享库存,但为每个渠道设置最低库存阈值,低于阈值时停止销售。
-- 渠道库存阈值配置
CREATE TABLE channel_inventory_threshold (
threshold_id BIGINT PRIMARY KEY COMMENT '阈值ID',
hotel_id BIGINT NOT NULL COMMENT '酒店ID',
room_type_id BIGINT NOT NULL COMMENT '房型ID',
channel_id INT NOT NULL COMMENT '渠道ID',
threshold_type TINYINT NOT NULL COMMENT '阈值类型: 1-绝对值 2-百分比',
threshold_value INT NOT NULL COMMENT '阈值: 10(间) 或 15(百分比)',
effective_date DATE NOT NULL,
expire_date DATE NOT NULL,
INDEX idx_channel (channel_id, hotel_id)
) COMMENT '渠道库存阈值表';库存扣减分发逻辑:
public class InventoryDistributionService {
/**
* 分布式库存扣减
*
* @param channelCode 渠道编码
* @param request 预订请求
* @return 是否扣减成功
*/
@Transactional
public boolean deductInventory(String channelCode, BookingRequest request) {
HotelChannelMapping mapping = hotelChannelMapper.findByHotelAndChannel(
request.getHotelId(), channelCode);
if (mapping == null) {
throw new BusinessException("该渠道未配置");
}
LocalDate date = request.getCheckInDate();
switch (mapping.getInventoryMode()) {
case 1: // 配额分配
while (date.isBefore(request.getCheckOutDate())) {
int affected = channelAllocationMapper.decreaseAllocation(
mapping.getMappingId(), date, 1);
if (affected == 0) {
throw new BusinessException("渠道配额不足");
}
date = date.plusDays(1);
}
break;
case 2: // 实时库存
while (date.isBefore(request.getCheckOutDate())) {
int affected = inventoryCalendarMapper.decreaseInventory(
request.getRoomTypeId(), date, 1);
if (affected == 0) {
throw new BusinessException("实时库存不足");
}
date = date.plusDays(1);
}
break;
case 3: // 阈值模式
while (date.isBefore(request.getCheckOutDate())) {
// 先检查是否高于阈值
int available = inventoryCalendarMapper.getAvailableInventory(
request.getRoomTypeId(), date);
int threshold = channelInventoryThresholdMapper.getThreshold(
mapping.getMappingId(), date);
if (available <= threshold) {
throw new BusinessException("渠道库存已达阈值下限");
}
// 扣减共享库存
int affected = inventoryCalendarMapper.decreaseInventory(
request.getRoomTypeId(), date, 1);
if (affected == 0) {
throw new BusinessException("共享库存不足");
}
date = date.plusDays(1);
}
break;
}
return true;
}
}5.3 价格一致性管理
多渠道分销时,价格管理是最敏感的问题。需要平衡渠道差价与价格一致性。
价格策略表:
-- 渠道价格策略表
CREATE TABLE channel_price_policy (
policy_id BIGINT PRIMARY KEY COMMENT '策略ID',
hotel_id BIGINT NOT NULL COMMENT '酒店ID',
room_type_id BIGINT NOT NULL COMMENT '房型ID',
channel_id INT NOT NULL COMMENT '渠道ID',
price_model TINYINT NOT NULL COMMENT '定价模式: 1-B2B底价 2-B2C售价 3-渠道自主价',
base_price BIGINT NOT NULL COMMENT '底价(分,平台采购价)',
suggested_price BIGINT COMMENT '建议售价(分)',
min_allowed_price BIGINT COMMENT '最低允许售价(分,防止渠道杀价)',
max_allowed_price BIGINT COMMENT '最高允许售价(分,防止渠道抬价)',
commission_rate INT COMMENT '佣金比例(百分比)',
commission_type TINYINT DEFAULT 1 COMMENT '佣金计算方式: 1-按比例 2-按固定金额',
price_sync_mode TINYINT DEFAULT 1 COMMENT '价格同步模式: 1-自动同步 2-手动审核后同步',
create_time DATETIME NOT NULL,
UNIQUE KEY uk_channel_room (channel_id, room_type_id, hotel_id)
) COMMENT '渠道价格策略表';B2B与B2C差价管理:
价格体系层级:
供应商底价 (Cost Price)
│
▼
平台采购价 (B2B Price) = 底价 + 平台服务费
│
├── 批发商渠道价 = B2B Price + 批发商佣金
│
└── B2C渠道价
│
├── OTA渠道价 = B2B Price x (1 + OTA佣金比例)
│
├── 自有官网价 = B2B Price x (1 + 官网利润率)
│
└── 会员专享价 = 官网价 x 会员折扣率
价格一致性保障:
1. 各渠道最低/最高限价约束
2. 定期价格巡检: 比对各渠道前台卖价
3. 价格违规预警: 发现渠道低于最低限价时触发告警
4. 自动调价: 根据竞品价格/入住率动态调整建议售价价格同步实现示例:
@Component
public class PriceSyncService {
/**
* 价格批量同步到各渠道
*/
@Scheduled(cron = "0 */15 * * * ?") // 每15分钟执行
public void syncPriceToChannels() {
// 获取最近有价格变动的酒店列表
List<Long> changedHotelIds = priceChangeLogMapper.getRecentChanged(15);
for (Long hotelId : changedHotelIds) {
List<HotelChannelMapping> channels = hotelChannelMapper
.findByHotelId(hotelId);
for (HotelChannelMapping channel : channels) {
if (!channel.getIsSyncEnabled()) continue;
try {
// 构建渠道价格数据
ChannelPriceData priceData = buildChannelPriceData(
hotelId, channel);
// 调用渠道API同步价格
ChannelSyncClient client = getChannelClient(
channel.getChannelId());
boolean success = client.syncPrice(priceData);
if (success) {
priceChangeLogMapper.markSynced(hotelId,
channel.getChannelId());
} else {
// 记录同步失败,后续重试
syncFailLogMapper.insert(hotelId,
channel.getChannelId(), "同步失败");
}
} catch (Exception e) {
log.error("价格同步失败 hotelId={} channelId={}",
hotelId, channel.getChannelId(), e);
syncFailLogMapper.insert(hotelId,
channel.getChannelId(), e.getMessage());
}
}
}
}
private ChannelPriceData buildChannelPriceData(Long hotelId,
HotelChannelMapping channel) {
ChannelPriceData data = new ChannelPriceData();
data.setHotelId(channel.getChannelHotelId());
// 获取该渠道各房型各日期的价格
List<ChannelRoomPrice> prices = new ArrayList<>();
List<RoomType> roomTypes = roomTypeMapper.findByHotelId(hotelId);
for (RoomType roomType : roomTypes) {
ChannelPricePolicy policy = channelPricePolicyMapper
.findByChannelAndRoom(channel.getChannelId(),
roomType.getRoomTypeId());
if (policy == null) continue;
// 获取未来30天的房价日历
List<RateCalendar> rates = rateCalendarMapper
.findFutureDays(roomType.getRoomTypeId(), 30);
for (RateCalendar rate : rates) {
long channelPrice = calculateChannelPrice(
rate.getRoomPrice(), policy);
prices.add(new ChannelRoomPrice(
roomType.getRoomTypeId(),
rate.getDate(),
channelPrice));
}
}
data.setPrices(prices);
return data;
}
private long calculateChannelPrice(long basePrice,
ChannelPricePolicy policy) {
long price = basePrice;
if (policy.getPriceModel() == 1) {
// B2B模式: 返回底价
return policy.getBasePrice();
}
// 应用渠道加价/降价
price = basePrice + policy.getPriceAdjust();
// 限价约束
if (policy.getMinAllowedPrice() != null
&& price < policy.getMinAllowedPrice()) {
price = policy.getMinAllowedPrice();
}
if (policy.getMaxAllowedPrice() != null
&& price > policy.getMaxAllowedPrice()) {
price = policy.getMaxAllowedPrice();
}
return price;
}
}5.4 渠道收益管理
通过渠道收益分析,优化各渠道的库存分配和定价策略。
-- 渠道收益分析视图
CREATE VIEW channel_revenue_analysis AS
SELECT
c.channel_name,
hcm.hotel_id,
COUNT(DISTINCT so.sub_order_id) AS booking_count,
SUM(so.sub_amount) AS total_revenue,
AVG(so.sub_amount) AS avg_order_value,
SUM(so.sub_amount) * AVG(ch.commission_rate) / 100 AS total_commission,
COUNT(DISTINCT CASE WHEN so.booking_status = 0 THEN so.sub_order_id END) AS pending_confirm,
COUNT(DISTINCT CASE WHEN so.sub_status = 5 THEN so.sub_order_id END) AS cancel_count
FROM sales_channel c
JOIN hotel_channel_mapping hcm ON c.channel_id = hcm.channel_id
JOIN travel_sub_order so ON so.supplier_id = hcm.hotel_id
LEFT JOIN channel_price_policy ch
ON ch.channel_id = c.channel_id AND ch.hotel_id = hcm.hotel_id
WHERE so.product_type = 4 -- 酒店产品
GROUP BY c.channel_name, hcm.hotel_id;该视图可用于运营决策:如果某渠道佣金率高但订单转化率低,可考虑缩减其库存配额;反之,低佣金高转化的渠道应获得更多库存倾斜。
6. 总结
旅游电商与酒店预订系统是一个典型的复杂业务系统,其核心设计要点可以归纳如下:
| 领域 | 核心挑战 | 设计要点 |
|---|---|---|
| 产品体系 | 多品类异构管理 | 统一产品模型 + 品类扩展字段;SPU + SKU 分层设计 |
| 订单体系 | 多品类统一履约 | 主订单+子订单拆分;品类独立状态机;统一支付分账 |
| 供应链 | 多层供应商协同 | 按品类配置采购模式;灵活结算周期;库存同步策略差异化 |
| 房价库存 | 实时性+动态定价 | RatePlan + 库存日历双维度;按日期粒度存储;规则引擎支持多种优惠策略 |
| 房态管理 | 精细化管控 | OOO/OOS/CTA/CTD 多态管理;超售模型基于历史数据动态调整 |
| 多渠道分发 | 库存一致性与价格平衡 | 配额/实时/阈值三种分配模式;B2B2C 分层定价;自动价格同步与巡检 |
该系统对数据一致性要求极高,特别是在库存扣减和价格计算环节,需要综合运用分布式锁、乐观锁、本地事务和补偿机制来保证系统的正确性和可用性。