飞猪国际机票 B2B 分销系统 — 架构全景分析 · Node.js 视角解读
连接外部供应商(Pkfare、Traveloka)和内部交易系统,处理搜索、下单、出票、改签、退票全链路
点击每层展开详细说明、关键文件和 Node.js 类比
Spring Boot Application 入口 + MainController 健康检查。相当于 Node.js 中的 app.listen() + 路由注册。
// Node.js 类比 import express from 'express' const app = express() app.get('/health', (req, res) => res.json({ status: 'UP' })) app.listen(8080) // ← 相当于 Spring Boot 的 @SpringBootApplication
纯接口 + DTO 定义,不包含任何实现。其他应用通过此 jar 包调用本应用的 HSF 服务。相当于发一个 npm types 包或 OpenAPI schema。
// Java: HSF 接口定义 public interface SearchQueryAppService { ResultDO<SearchResDTO> search(SearchReqDTO req); ResultDO<EnrichResDTO> enrich(EnrichReqDTO req); } // Node.js 类比: TypeScript 接口 interface SearchQueryService { search(req: SearchReq): Promise<Result<SearchRes>> enrich(req: EnrichReq): Promise<Result<EnrichRes>> }
实现 Client 层的接口,编排业务流程。不含核心逻辑,只做"指挥"。相当于 Express/Koa 的 controller。
系统的"大脑",所有核心业务规则。不依赖外部框架,纯业务逻辑。
最大的模块,隔离所有外部系统。分三个方向:input 接收请求、output 调用下游、inner 内部工具。
实现 Domain 层的 Repository 接口,操作 MySQL(TDDL 分库分表)、Redis 缓存、Rye 配置。
跨模块共享的 DTO/Enum/常量,通用工具函数,日志监控。
点击卡片查看每个领域的完整链路、Domain 服务和关键约束
点击每个步骤查看详细说明和代码
点击接口行查看请求/响应结构和调用链路
| 方法 | 接口 | 说明 | Controller → Service |
|---|---|---|---|
| POST | /search | 航班搜索 | PkfareSearchController → PkfareSearchService |
| POST | /qte | 询价 (Quote) | PkfareQteController → PkfareQteService |
| POST | /order | 创建订单 | PkfareOrderController → PkfareOrderService |
| POST | /ticketing | 出票通知 | — → PkfareTicketIssuanceService |
| POST | /unissuedOrder | 待出票查询 | PkfareUnissuedOrderController |
| POST | /orderStatusPush | 状态推送 | PkfareOrderStatusPushController |
| POST | /flightChange | 航变通知 | — → PkfareFlightChangeNotifyService |
| 方法 | 接口 | 说明 | Controller → Service |
|---|---|---|---|
| POST | /token | OAuth Token | TravelokaTokenController → TravelokaTokenService |
| POST | /search | 航班搜索 | TravelokaSearchController → TravelokaSearchService |
| POST | /preOrderVerify | 预下单校验 | TravelokaPreOrderVerifyController |
| POST | /order | 创建订单 | TravelokaOrderController → TravelokaOrderService |
| POST | /pay | 支付 | TravelokaPayController → TravelokaPayService |
| GET | /orderDetail | 订单详情 | TravelokaOrderDetailController |
| POST | /cancel | 取消订单 | TravelokaCancelController → TravelokaCancelService |
| POST | /ancillarySearch | 附加服务搜索 | TravelokaAncillarySearchController |
点击事件查看处理逻辑
点击查看技术细节
@HSFProvider( serviceInterface = SearchQueryAppService.class, serviceVersion = "${spring.hsf.version}", clientTimeout = 60000) public class SearchQueryAppServiceImpl implements SearchQueryAppService { @Autowired private SearchRestrictDomainService restrictSvc; @Override public ResultDO<SearchResDTO> search( SearchReqDTO req) { // 业务编排... return ResultDO.buildSuccessResult(res); } }
// tRPC / NestJS 风格 @Injectable() export class SearchService { constructor( private restrictSvc: SearchRestrictService, ) {} async search( req: SearchReq): Promise<Result<SearchRes>> { // 业务编排... return { success: true, data: res } } } // HSF ≈ gRPC/tRPC: RPC 接口框架 // @HSFProvider ≈ @GrpcMethod / router // @Autowired ≈ constructor DI // ResultDO<T> ≈ { success, data, code }
public ResultDO<T> book(BookReqDTO req) { try { req.check(); // 参数校验 qpsRestrict(...); // QPS限流 validateCred(...); // 证件校验 lock = lock(key, 300); // 分布式锁 result = adaptor.book(...); return ResultDO.buildSuccess(result); } catch (MultiLanguageBizException e) { return ResultDO.buildFail( e.getErrorCode(), e.translate(req.getLanguage())); } finally { if (lock != null) unlock(lock); LogContextUtils.printLogAndRemove(); } }
async book(req: BookReq): Promise<Result<T>> { try { validate(req, schema) // zod await limiter.check(key) // rate-limit validatePassport(req.pax) // 证件 const release = await redlock .lock(`book:${req.orderNum}`, 300_000) try { const result = await adaptor.book(...) return { success: true, data: result } } finally { await release() } } catch (e) { if (e instanceof BizError) return { success: false, code: e.code, msg: t(e.code, req.lang) } throw e } }
public class ChangeSaleOrderAggregate extends BaseAggregate { private ChangeSaleOrderEntity entity; public void confirmPayment( PaySucceededParam param) { if (!canPay()) { throw new AggregateException( "状态不允许支付"); } entity.setStatus(PAID); entity.setPayTime(new Date()); } public void transferSuccess(...) { ... } public void refundFailed(...) { ... } }
// 聚合根 = 包含业务规则的实体 // 所有状态变更必须通过聚合根方法 class ChangeSaleOrder { private entity: ChangeOrderEntity confirmPayment(p: PaySucceeded) { if (!this.canPay()) throw new DomainError('状态不允许') this.entity.status = 'PAID' this.entity.payTime = new Date() } transferSuccess(...) { ... } refundFailed(...) { ... } } // 核心:外部不能直接 order.status = X // 必须走 order.confirmPayment() 方法
// Domain 层 - 接口 public interface SaleOrderRepository extends Repository { SaleOrderAggregate getById(Long id); void save(SaleOrderAggregate agg); } // Infrastructure 层 - 实现 public class SaleOrderRepositoryImpl implements SaleOrderRepository { @Autowired SaleOrderDAO dao; // MyBatis public SaleOrderAggregate getById(Long id) { SaleOrderPO po = dao.selectById(id); return converter.toAggregate(po); } }
// 接口定义 interface SaleOrderRepo { getById(id: number): Promise<SaleOrder> save(order: SaleOrder): Promise<void> } // Prisma/Drizzle 实现 class SaleOrderRepoImpl implements SaleOrderRepo { async getById(id: number) { const row = await db.saleOrder .findUnique({ where: { id } }) return toAggregate(row) } } // PO(数据库行) ↔ Aggregate(业务对象) // MyBatis XML ≈ Prisma schema // TDDL 分库分表 ≈ Vitess/PlanetScale