优先级: P0(核心交易模块)
开发周期: 3 周
平台核心引擎,负责接收代理商查价请求,并发查询多个供应商,聚合结果后返回最优报价。要求高性能(P99 < 2s)、高可用(降级策略)、智能缓存。
查价链路拆分为三个阶段,每个阶段有明确的职责边界和执行时机:
┌─────────────────────────────────────────────────────────────┐
│ 查价请求入口 │
└──────────────────────┬──────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ Phase 1: 查价前 (Pre-Query) │
│ ───────────────────────────────────────── │
│ 职责:过滤、拦截、准备,决定是否需要查价 │
│ │
│ 1. Gateway 鉴权 + 限流 │
│ 2. 风控检查(信用额度、流量异常) │
│ 3. 熔断检查(供应商/代理商维度) │
│ └─ 熔断状态 → 直接跳过该供应商,不进入查价 │
│ 4. 可见性过滤(白名单/黑名单) │
│ 5. 缓存检查 │
│ ├─ L1 本地缓存命中 → 直接返回(不走后续阶段) │
│ └─ L2 Redis 聚合缓存命中 → 直接返回 + 回填 L1 │
│ 6. 供应商映射解析 │
│ └─ 平台酒店ID → 对应可售卖的供应商酒店ID列表 │
│ 7. 渠道配置加载(定价策略、可用供应商、请求配额) │
│ │
│ → 输出:有效供应商列表 + 查价参数 │
│ → 阻断条件:熔断、信用不足、所有缓存命中 │
└──────────────────────┬──────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ Phase 2: 查价 (Query) │
│ ───────────────────────────────────────── │
│ 职责:并发查询供应商,获取原始报价数据 │
│ │
│ 1. 并发查价(goroutine + errgroup) │
│ ├─ 直连供应商 → 调用 SA 实时查询 API(带超时 3s) │
│ │ ├─ L2 原始报价缓存命中 → 直接使用(TTL 60s) │
│ │ └─ 未命中 → 调用远程 API → 缓存结果 │
│ │ │
│ └─ 直采供应商 → 查本地库存(inventory_snapshots + Redis) │
│ ├─ available_rooms IS NULL → 有价即有房 │
│ ├─ available_rooms > 0 → 返回本地定价 │
│ └─ available_rooms = 0 → 不返回该选项 │
│ │
│ 2. 降级策略 │
│ ├─ 单个供应商超时 → 跳过,返回其他供应商结果 │
│ ├─ 单个供应商 5xx → 跳过,自动重试 1 次 │
│ ├─ 所有供应商超时 → 返回 Redis 缓存(标记"可能非实时") │
│ └─ Redis 不可用 → 退化纯实时查询,仅靠 L1 兜底 │
│ │
│ → 输出:各供应商原始报价列表(不含加价/动态定价/促销) │
└──────────────────────┬──────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ Phase 3: 查价后 (Post-Query) │
│ ───────────────────────────────────────── │
│ 职责:价格计算、聚合、排序,返回最终结果 │
│ │
│ 1. 结果聚合 │
│ ├─ 去重(同一房型可能同时有直连和直采报价,都保留) │
│ ├─ 排序(按价格、推荐度、供应商优先级) │
│ └─ 分页 │
│ 2. Markup 加价(Redis 缓存规则) │
│ └─ 最终价 = 采购价 × (1 + markup%) + 固定加价 │
│ 3. 动态定价调整(Redis 缓存规则) │
│ └─ 按入住日期/提前天数/连住天数叠加规则 │
│ 4. 促销匹配(Redis 缓存促销) │
│ └─ 多促销不可叠加,取最优 │
│ 5. 汇率换算(Redis 缓存汇率) │
│ 6. 写入缓存 │
│ ├─ L2 Redis: price:agg:* (TTL 5min) │
│ └─ L1 本地缓存 (TTL 30s) │
│ 7. 异步写查价日志(ClickHouse) │
│ │
│ → 输出:最终报价列表(含加价、动态定价、促销、汇率) │
└─────────────────────────────────────────────────────────────┘
设计要点 - Phase 1 的阻断操作(熔断、可见性、信用检查)在查价前完成,避免浪费供应商查询资源 - Phase 2 只做数据获取,不做价格计算,保证原始数据纯净 - Phase 3 的所有价格变换操作(加价/动态定价/促销/汇率)都是实时计算的,不缓存中间结果 - 缓存策略:原始报价(price:raw)缓存不含加价,聚合结果(price:agg)缓存含最终价 - 促销/加价规则变更时,只需清理 price:agg 缓存,price:raw 不受影响
Pre-Query 输出 Query 输出 Post-Query 输出
───────────── ───────── ───────────────
· 有效供应商列表 · 供应商原始报价列表 · 最终报价列表
· 查价参数 · 各供应商响应状态 · 排序后结果
· 渠道配置 · 缓存命中标记 · 分页信息
· 熔断/过滤决策 · 降级标记 · 查价元数据
| Phase | 操作 | 依赖模块 | 说明 |
|---|---|---|---|
| 1 查价前 | Gateway 鉴权 + 限流 | API Gateway | JWT/HMAC + 速率限制 |
| 风控检查 | Risk Control | 信用额度、流量异常 | |
| 熔断检查 | Sales Management | 供应商/代理商熔断状态 | |
| 可见性过滤 | Sales Management | 白名单/黑名单 | |
| 缓存检查 | Query Engine (L1/L2) | 聚合缓存命中直接返回 | |
| 供应商映射 | Mapping | 平台酒店 → 供应商酒店列表 | |
| 渠道配置 | Sales Management | 定价策略、配额 | |
| 2 查价 | 直连供应商查询 | Supplier Adapter (SA) | 实时 API 调用 |
| 直采供应商查询 | Inventory | 本地库存查询 | |
| 原始报价缓存 | Query Engine (L2 Redis) | price:raw 缓存 | |
| 降级策略 | Query Engine | 超时/错误处理 | |
| 3 查价后 | Markup 加价 | Pricing | 基础加价规则 |
| 动态定价 | Pricing Advanced | 动态定价引擎 | |
| 促销匹配 | Pricing Advanced | 促销引擎 | |
| 汇率换算 | Pricing Advanced | 汇率服务 | |
| 结果聚合排序 | Query Engine | 去重/排序/分页 | |
| 缓存写入 | Query Engine (L1/L2) | price:agg 缓存 | |
| 查价日志 | BI Analytics | ClickHouse 异步写入 |
| 场景 | 策略 | 用户感知 |
|---|---|---|
| 单个供应商超时 | 跳过该供应商,返回其他供应商结果 | 正常返回,部分供应商可能缺失 |
| 单个供应商 5xx | 跳过该供应商,自动重试 1 次 | 同上 |
| 所有供应商超时 | 返回 Redis 缓存结果(即使过期),标记 "数据可能非实时" | 降级返回 |
| Redis 不可用 | 退化为纯实时查询,仅靠 L1 本地缓存兜底 | 稍慢但可用 |
| 系统过载 | 触发限流,返回 429 Too Many Requests | 提示稍后重试 |
查价引擎(Phase 2)根据供应商的 connection_mode 采用不同的查询策略:
| connection_mode | 查询方式 | 延迟 | 数据来源 |
|---|---|---|---|
| direct_connect | 调用供应商适配器(SA)实时查询 | 较高(200ms-3s) | 供应商 API 实时返回 |
| direct_procure | 查本地 inventory_snapshots + Redis(通过 DirectProcureAdapter) | 极低(<10ms) | 本地数据库/Redis |
注意:直采数据有有效期(sync_validity_minutes),过期数据不返回 详细设计见: 价格同步模块 supplier-sync.md
查价请求
├─ 遍历酒店关联的所有供应商
│ ├─ direct_connect 供应商 → 调用 SA 实时查询 API(带超时)
│ │ ├─ 返回有价格 → 视为有房(有价即有房,无库存数量)
│ │ └─ 超时/错误 → 走降级策略(返回缓存或跳过)
│ │
│ └─ direct_procure 供应商 → 查询本地库存(inventory_snapshots + Redis)
│ ├─ available_rooms IS NULL → 有价即有房,返回本地定价
│ ├─ available_rooms > 0 → 返回本地定价(有明确库存数量)
│ └─ available_rooms = 0 → 不返回该选项
│
├─ 聚合结果:合并 direct_connect 和 direct_procure 的结果
│ ├─ direct_connect 结果:available_rooms 可能为空(表示"有价即有房")
│ ├─ direct_procure 结果:available_rooms 为具体数字
│ └─ 去重:同一房型可能同时有直连和直采报价,都保留让用户选择
│
└─ 加价策略 → 排序 → 返回
注意:direct_connect 的缓存策略与 direct_procure 不同。direct_connect 缓存的是查询结果(价格),有效期短(5min);direct_procure 的库存数据由库存模块独立维护,查价引擎直接读取。
-- 查价请求日志表(写入 ClickHouse 用于分析,PG 做短期热数据)
CREATE TABLE price_search_logs (
id BIGSERIAL PRIMARY KEY,
trace_id VARCHAR(64) NOT NULL, -- 链路追踪ID
agent_code VARCHAR(32) NOT NULL, -- 代理商编码
channel_code VARCHAR(32), -- 渠道编码
hotel_id BIGINT, -- 平台酒店ID
check_in DATE NOT NULL,
check_out DATE NOT NULL,
adults SMALLINT DEFAULT 2,
children SMALLINT DEFAULT 0,
room_count SMALLINT DEFAULT 1,
currency VARCHAR(3) DEFAULT 'CNY',
supplier_codes VARCHAR(255), -- 查询了哪些供应商
result_count INT DEFAULT 0, -- 返回结果数量
min_price DECIMAL(12, 2), -- 最低价
cache_hit BOOLEAN DEFAULT false, -- 是否命中缓存
phase_blocked VARCHAR(32), -- 被阻断的阶段(pre_query/query/post_query/null)
response_ms INT, -- 响应耗时
error_code VARCHAR(32), -- 错误码
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_search_logs_hotel_date ON price_search_logs (hotel_id, check_in, check_out);
CREATE INDEX idx_search_logs_agent ON price_search_logs (agent_code, created_at);
-- Redis 缓存 Key 设计
# 原始供应商报价缓存: price:raw:{supplier_code}:{supplier_hotel_id}:{check_in}:{check_out}
# TTL: 60s (直连) / 30s (直采)
# Value: 原始报价 JSON(不含 markup/动态定价/促销)
# 聚合结果缓存: price:agg:{hotel_id}:{check_in}:{check_out}:{adults}:{channel_code}
# TTL: 300s (5min)
# Value: JSON数组,每个元素包含供应商、房型、最终价格(含markup+动态定价+促销)、库存等
# 供应商映射缓存: mapping:suppliers:{hotel_id}
# TTL: 3600s (1h)
# Value: 该酒店可售卖的供应商列表(含 supplier_hotel_id、connection_mode)
# 供应商熔断状态: circuit:{supplier_code}
# TTL: 动态(open 状态 30s,half_open 60s)
# Value: 状态JSON(state/failure_count/last_failure)
# 代理商熔断状态: circuit:{agent_code}
# TTL: 动态
# Value: 状态JSON
# 代理商信用额度: credit:{agent_code}
# TTL: 300s (5min)
# Value: 信用额度JSON(available/frozen/limit)
# 渠道定价配置: channel:pricing:{channel_code}
# TTL: 600s (10min)
# Value: 渠道定价策略JSON
# 详细见系统架构文档 缓存架构章节的完整 Key 设计和 TTL 策略
# 查价 API(核心接口)
POST /api/v2/prices/search
# 请求体:
{
"hotel_id": "HPT-001234",
"check_in": "2025-03-01",
"check_out": "2025-03-03",
"adults": 2,
"children": 0,
"room_count": 1,
"currency": "CNY",
"guest_country": "CN",
"rate_plan_codes": [], # 可选: 指定价格计划
"supplier_codes": [], # 可选: 指定供应商
"client_reference": "req-xxx" # 代理商请求号
}
# 响应体:
{
"request_id": "plat-xxx",
"hotel_id": "HPT-001234",
"hotel_name": "Shanghai Marriott Hotel",
"check_in": "2025-03-01",
"check_out": "2025-03-03",
"nights": 2,
"currency": "CNY",
"options": [
{
"option_id": "OPT-001",
"supplier_code": "HOTELBEDS",
"room_type_code": "DBL",
"room_type_name": "豪华大床房",
"meal_plan": "BB",
"rate_plan_code": "BAR",
"gross_price": 1200.00, # 售价(含佣金)
"net_price": 1000.00, # 净价
"commission": 200.00, # 佣金
"currency": "CNY",
"available_rooms": 5,
"cancellation_policy": {
"type": "non_refundable"
},
"supplier_ref": "HB-123456", # 供应商侧报价引用号
"valid_until": "2025-01-20T10:00:00Z" # 报价有效期
}
],
"meta": {
"total_options": 3,
"search_time_ms": 850,
"suppliers_queried": ["HOTELBEDS", "EXPEDIA", "BEDSONLINE"],
"suppliers_skipped": ["AGODA"], # Phase 1 阻断的供应商(熔断等)
"cache_hit": false,
"phase_trace": { # 三阶段耗时追踪
"pre_query_ms": 5,
"query_ms": 820,
"post_query_ms": 25
}
}
}
# 批量查价 API(多酒店)
POST /api/v2/prices/batch-search
# 报价验证 API(下单前验证报价是否仍有效)
POST /api/v2/prices/verify
| 关联模块 | 关系 |
|---|---|
| API 网关 | 入口,鉴权和限流(Phase 1) |
| 酒店基地 | 获取酒店信息用于返回 |
| 匹配管理 | Phase 1 获取供应商映射关系 |
| 价格管理 | Phase 3 加价策略、币种转换 |
| 进阶定价 | Phase 3 动态定价调整、促销匹配、汇率换算 |
| 销售管理 | Phase 1 熔断检查、可见性过滤、渠道配置 |
| 供应商适配器 | Phase 2 direct_connect 供应商调用 SA 实时查询 |
| 库存管理 | Phase 2 direct_procure 供应商查询本地库存 |
| 风控体系 | Phase 1 信用额度检查、频率限制 |
| 订单管理 | 报价验证、下单入口 |
| 数据智能 | 查价日志用于分析 |