ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

开源航空订票系统需求与流程设计:从查询到出票的完整状态机实践

开源航空订票系统需求与流程设计:从查询到出票的完整状态机实践 航空订票系统这种项目一听“需求与流程设计”很多开发者的第一反应是“这不就是个CRUD吗”。但真正上手去做你会发现查询、下单、支付、出票、退改签这一条链路里到处是坑。我把这个开源项目的需求文档、流程图、数据库设计和核心状态机整理开放出来就是想让想练手业务系统的同学能有一份贴近真实工程的可参考素材。这个开源项目不追求花哨的技术栈重点在把业务逻辑想清楚。它适合正在做毕业设计、准备面试项目、或者刚进入企业接触业务系统的后端同学去阅读。哪怕你不写代码作为产品或者测试把里面的流程设计过一遍也会对航空订票这类预订交易系统有更具体的认知。1. 项目定位与需求全景图1.1 为什么值得做开源航空订票系统航旅预订属于典型的交易型业务系统复杂度比普通的单商品电商要高。它要处理多城市、多日期、多航班、多舱位的动态库存还要对接支付、退款、异步出票、行程通知等环节。这种复杂度恰恰是练习系统设计的好素材。我在整理这个开源项目时想的不是做一个只能“查航班、下订单”的玩具而是尽可能把真实业务里会碰到的需求收敛进来。比如不同客票的退改规则、订单超时未支付怎么处理、支付回调重复通知怎么保证幂等、出票失败怎么补偿。这些需求单独看都不难但组合在一起就需要一个清晰的需求框架来托底。这个项目对学习者的帮助主要有三点一是能直观理解一个业务系统从需求到流程再到数据模型是怎么逐步落地的二是可以通过状态机和接口设计理解订单生命周期管理的关键三是可以把这个项目作为模板迁移到酒店预订、门票预约等同类交易系统。1.2 角色与核心能力矩阵从使用角色来看这个系统分为乘客端和管理端两条主线。乘客端面向普通用户核心能力包括用户注册登录、航班查询、创建订单、在线支付、订单详情查看、退票改签申请。航班查询要支持按出发城市、到达城市、出发日期三个关键条件检索同时还能按时间段、航空公司、价格区间做二次筛选。用户下单时需要填写乘机人信息包括姓名、证件类型、证件号码。管理端面向航空公司的运营人员核心能力包括航班计划维护、航线创建、机型舱位配置、航班时刻调整、订单查询、退款审核。航班计划维护又涉及航线、航班号、执飞日期、起飞到达时刻、开放销售状态、各舱位可售数量等。这两个角色是所有后续流程设计的基础。需求阶段最常见的错误就是一上来直接画数据库表或者写Controller接口结果发现漏了后台管理员需要用的航班维护功能又重新改表。先做角色和用例梳理能避免后面返工。1.3 非功能需求不只是能跑通就行很多人做完功能演示就认为项目完成了但在航空订票场景下非功能需求比功能需求更容易暴露设计短板。首先是高并发。遇到节假日和促销节点航班查询和下单请求量会呈几十倍增长。这个开源项目建议在架构设计阶段就预留缓存和限流的概念虽然实现可以简化但设计文档里必须写清楚。其次是数据一致性。订票系统最容易出现两类一致性问题座位超售和订单状态错乱。座位超售的根源是并发扣减没有做控制订单状态错乱则是因为支付回调、用户取消、超时释放之间存在竞态条件。项目文档里把这些问题全部做了显式设计而不是等代码写崩了再补。再次是可审计性。每一笔订单的创建、支付、出票、改签、取消、退款都要有操作记录。后台运营人员、系统定时任务、用户前台请求三类操作来源都需要落到日志或操作流水表里。这一点很多初学者会忽略但真实生产环境下是刚需。2. 流程设计从查询到出票的核心链路2.1 航班查询与筛选最基础也最容易被低估的环节航班查询是用户接触系统的第一个页面也是承载并发量最高的接口。很多初次做这个项目的人会直接把航班信息存一张表然后写一个SQL按条件查询。这样做演示当然没问题但一旦需要支持“同一个航线每天多个航班”“有航班计划但当天不执飞”“各舱位可售数量动态变化”单表模型就撑不住了。我把这一块拆成了航线、航班计划、执飞实例三个层次。航线是静态定义比如北京到上海航班计划是某个航班号在哪些日期执飞比如CA1501每周一三五执飞执飞实例是某一天具体的航班它才有独立的库存和状态。用户查询时先根据出发城市和到达城市找到航线再匹配日期范围内的执飞实例最后返回航班列表和实时可售舱位。这个设计的好处是航班取消时只需要维护某一天的执飞实例状态不需要删掉整个航班计划增加一个新航班时也只需要新增一条班期规则不需要提前创建一年的执飞记录。从需求与流程设计的角度看这是把“一张航班表”升级为“具备时间维度的航班模型”的关键点。2.2 下单-支付-出票状态机怎么设计才稳订单是整个系统的核心聚合根。订单状态如果只设计成“待支付”和“已支付”两种后面接支付、对账、退改签时一定会被自己坑到。我在项目里把订单状态设计为完整的状态机。初始状态统一为待支付。用户提交订单后进入该状态此时系统会锁定所选航班对应舱位的座位并设置支付超时时间。如果在超时时间后仍未支付订单自动取消座位释放。用户在待支付状态主动取消订单称为用户取消同样会释放座位。用户支付成功后订单进入已支付状态。此时系统并不直接把票出给用户而是进入出票中状态由后台异步任务调用出票引擎。出票引擎在实际航司系统中对接的是GDS或者航司接口开源版本中为了便于演示会用本地模拟出票逻辑代替。出票成功订单变为已出票出票失败则触发自动退款流程。这张状态机图是整个需求与流程设计的核心。订单模块的每个方法都必须基于当前状态判断可执行的动作。比如已出票状态下不允许重复支付已取消状态下不允许再发起支付退款申请只有在已出票状态下才允许发起。状态流向清楚之后代码很难写乱。2.3 退改签流程经常被低估的重头戏退改签是航空订票系统里最容易出bug的地方因为规则多、组合多。我整理这个开源项目时把退改签规则单独拆成一张规则表而不是硬编码在代码里。规则表大致包含这几个维度客票类型经济舱特价、经济舱标准、头等舱、退改类型自愿退票、非自愿退票、自愿改期、距离航班起飞的天数区间、对应手续费比例或固定金额。例如特价票在起飞48小时前退票收取票面价80%的手续费起飞前48小时内不允许退票。具体规则每家航司不一样但作为需求设计规则表的模式是通用的。退票流程上用户提交退票申请后系统根据规则表计算应退金额生成退款记录。开源版本先进入退款审核状态管理端可以审核通过或驳回。审核通过后才真正执行退款操作同时更新订单状态和释放座位。改签流程类似需要先判断新航班是否还有对应舱位的库存有库存则锁定新座位再按规则计算改期费用。这个流程最能体现“需求设计”的价值。如果一上来就写退票接口很容易把退款金额、手续费、订单状态三个字段的联动逻辑漏掉最后算出来的金额对不上账。3. 数据模型与接口约定3.1 核心数据表设计思路需求与流程设计落到最后一定会转化为数据结构。这个开源项目的核心表我列出来供参考表名核心字段说明设计要点user用户ID、登录名、密码盐值、姓名、证件信息密码不可明文存储证件信息做脱敏展示airport机场三字码、城市、机场名称机场与城市是多对一关系route航线ID、出发机场、到达机场外键一条航线是城市对机场对的组合flight_schedule航班号、航线ID、班期、起飞/到达时刻班期用位图或字符串标记周几执飞flight_instance执飞实例ID、航班计划ID、执飞日期、状态取消航班只改此表状态cabin_stock执飞实例ID、舱位代码、总座位数、已售座位数库存扣减必须使用原子SQL或乐观锁fare_rule客票类型、退改规则JSON、价格策略规则尽量数据化避免代码写死orders订单号、用户ID、执飞实例ID、舱位、状态状态枚举必须和状态机设计一致passenger_info订单ID、乘机人姓名、证件号、票号一单可多乘机人但要支持部分退票payment_record订单ID、支付流水号、支付金额、状态用业务订单号做幂等键refund_resord订单ID、退款流水号、退款金额、审核人、状态退款状态必须闭环支持审核驳回operation_log操作来源、操作类型、业务单号、操作内容所有写操作都走日志记录订单表里“状态”这个字段在整个项目里使用频率最高建议使用数字枚举并与代码里的常量或枚举类一一对应不要用容易被误解的字符串。比如已支付与已出票在外界看都是用户侧已经付过款但是在系统内部必须严格区分否则对账时会把未出票的单子统计为有效票。3.2 对外API的核心约定这个开源项目的接口遵循REST风格约定统一返回结构。每个接口的正常返回包含业务码、消息和数据体异常时也会返回对应的业务码而不是直接把500错误抛给前端。接口方法说明关键入参/api/flights/searchPOST航班查询出发城市、到达城市、日期、舱位类型/api/ordersPOST创建订单执飞实例ID、舱位代码、乘机人列表/api/orders/{id}/payPOST发起支付订单ID、支付方式/api/payment/callbackPOST支付回调第三方支付流水号、订单号、支付结果/api/orders/{id}/cancelPOST取消订单订单ID、取消原因/api/orders/{id}/refundPOST申请退票订单ID、乘机人列表/api/orders/{id}GET查询订单详情订单ID支付回调接口是这里最需要注意的业务约束。第三方支付平台会异步通知结果并且会重试多次接口必须做到幂等。实现时先用订单号加支付流水号查本地记录如果已处理过就直接返回成功不重复更新订单状态。这部分在需求文档里要单独强调因为新手很容易忽略重复通知的问题。3.3 并发与座位库存设计座位库存是这个项目里最容易出并发问题的地方同时也是面试时最容易拿得出手的亮点。舱位库存表保存的是某个执飞日期的某个客舱等级对应的已售座位数和总座位数剩余座位数就是两者之差。用户下单时不能先查剩余数再做内存判断必须用原子操作扣减。推荐的做法是使用数据库的原子更新update cabin_stock set sold_count sold_count 1 where flight_instance_id ? and cabin_class ? and sold_count total_count。这样单条SQL就能保证不会超售执行后影响行数为0就代表没有库存了。如果系统访问量很大可以再引入Redis缓存库存但DB仍然是最终一致性的来源。下单时需要锁定座位但用户未必会支付。所以设计里必须有超时释放机制。开源项目里安排了一个定时任务每分钟扫描待支付状态且超过支付时效的订单将其状态改为已取消同时执行sold_count减一操作。这里还要考虑一个问题用户重复下单占用了多个座位超时后自然释放对真实业务来说这个体验并不好但作为基础版本先把资源释放的链路走通后续可以再扩展真人校验、防重复下单等逻辑。4. 从需求到可运行系统实操落地过程4.1 需求文档到用例拆解这个开源项目发布时我附上了完整的用例文档。核心用例包括查询可用航班、提交订单、支付订单、取消订单、申请退票、审核退票、维护航班计划、调整航班价格等。每个用例都写了主流程、扩展流程和异常流程。举例说明提交订单这个用例。主流程是用户先搜索航班、选择舱位、填写乘机人信息、提交订单、系统锁定座位、返回待支付订单。扩展流程是提交时库存不足系统提示无票。异常流程是用户连续双击提交形成两个订单系统需要在前端做防重复提交后端也需要在短时间内使用相同请求参数创建订单时返回已有的待支付订单而不是重复创建。把这些用例写清楚比直接贴代码更能帮助理解和交流。使用例能保证前后端对同一个业务流程的理解一致减少联调阶段的返工。4.2 先画流程状态图再画页面原型我自己在项目里踩过不少坑最大的一个心得是不要先打开代码编辑器写实体类先打开白板画流程图和状态图。特别是订单、支付、退款三条核心路径一定要把状态节点和触发动作画到没有疑问之后再动手。页面原型在这个项目里可以很简单用主流原型工具画几个关键页面就行。真正需要投入时间画的是业务流程图。比如支付回调到来时如果订单已经处于已取消状态这时不能直接把订单改成已支付而是要进入一笔新的待支付或者提示用户重新下单退票审核驳回后订单要回到已出票状态并且退还的座位不能重复释放。从需求与流程设计的角度来说流程图和状态图比页面原型重要得多。页面只是交互呈现状态图才是系统行为的内在约束。开源项目里我把这几张图整理成了电子文档作为代码仓库的一部分发布就是希望使用者先看图而不是先跑代码。4.3 最小可运行闭环的实现顺序给想基于这个开源项目做二次开发或者学习的人一个实现顺序建议。第一步建议先做航班查询这个功能不涉及支付和状态流转能快速跑通前后端联通。第二步做用户认证和创建订单此时把座位库存的原子扣减加进去体会一下库存控制的逻辑。第三步做支付回调Mock和订单状态流转这是最核心的部分。第四步做退改签流程和管理端的审核功能。大概的顺序就是你先把一条能走通的主链路搭起来也就是查询到出票然后再加入退票、改签、超时释放这些分支链路。不要试图第一版就把所有功能全部完成分支逻辑很容易在初期拖垮整体进度。这个项目发布的状态已经是完整跑通的状态可以直接作为课程设计参考也可以在此基础上继续扩展。5. 常见问题与排查技巧实录5.1 订单状态不一致支付回调与订单状态不同步开发阶段和试用阶段最容易出现的问题就是用户已经支付成功但订单状态还停留在待支付。排查时先看支付回调有没有被正确接收最常见的原因有三个回调接口地址配置错误、回调处理里没有正确使用事务或幂等键、回调解析时因为签名校验失败被抛弃。建议在日志里增加支付回调原始报文打印同时表里记录第三方支付流水号。这样收到用户反馈后可以按订单号反查支付记录确认是回调缺失还是逻辑处理异常。定时对账机制也需要写进去每天把本地为已支付但支付平台查不到的单子捞出来核对。5.2 座位超售与并发扣减有些同学实现座位扣减时是先select查剩余座位数再在应用层判断大于0最后update更新已售数量。这种写法在并发情况下一定会超售。正确的做法是前面提到的单条原子update语句而且扣减前不用查询。另外还要注意扫码重复提交的问题。用户在下单页快速点了两次提交按钮可能生成两笔订单占用两个座位。解决方案是前端提交按钮置灰后端同时按用户ID和执飞实例ID做短时间重复单校验。这类问题在演示环境不太容易暴露但在真正的并发请求下非常致命。5.3 时区与航班日期显示问题航班日期和起飞时间涉及多个时区很多人会忽略。数据库如果直接存本地时间之后做国际化展示时就会乱套。这个项目里约定数据库统一存UTC时间接口返回ISO8601字符串前端根据用户本地时区做展示。在排查问题过程中看到过很多次日志里的时间比真实时间晚8小时的情况基本都是时区没有统一。排查时间问题时先看日志里的时间戳带不带时区标识再确认数据库连接URL里serverTimezone参数是否正确。这类问题在真实项目中很常见但不影响业务流程正确性所以常被归为“看起来不影响功能”的隐患。5.4 数据安全与操作审计开源项目必须强调安全红线。用户密码不能明文存储至少要做加盐哈希再入库推荐直接使用成熟加密库。乘机人证件号、手机号等敏感信息在接口返回时要做脱敏处理日志里也不要打印完整原文。支付接口必须有签名机制支付回调要做验签。操作审计这块建议做成一个独立模块后台管理员的所有关键操作比如审核退票、调整航班状态、修改价格都记录到操作日志表。这个模块本身不复杂但能极大提升系统的可追溯性后期排查问题的时候作用很大。最后再分享两个小技巧我自己在整理这个开源项目时最大的体会是流程设计文档比代码本身更有复用价值。代码逻辑每个项目不同但订单状态机、库存扣减规则、支付幂等处理这些设计思想互相之间是可以迁移的。你如果学会了这里面的思路再去做酒店预订、景点门票、活动票务会发现大部分东西都能直接复用。第二个建议是看完文档后一定要自己动手把状态机画一遍再对照开源代码看实现是否一致。只看不画很容易以为自己懂了真让你写的时候还是会在支付回调、超时释放这些点上卡住。希望你在这个项目里获得的不是一堆可运行的代码而是把复杂业务拆清楚的能力。
返回列表