
简介这份PDF面向软件工程学习者与UML建模入门者围绕航空订票系统这一经典案例讲解如何用统一建模语言完成从需求到部署的完整设计。内容涵盖用例图、类图、包图、顺序图、协作图、状态图、活动图、组件图与部署图九类图表并结合新用户注册、登录验证、航班查询、机票预订、退票及信用评价联动等业务场景逐一展开帮助读者理解参与者与系统功能、类与类之间的继承依赖关系以及对象交互与状态流转的表达方式。资源包共1个PDF文件大小约2.04MB篇幅紧凑适合作为课程设计、实验报告或建模练习的参考范本。目前已有1958人学习下载读者可借此掌握用Rose绘制各类UML图的基本思路理清复杂系统的模块划分与业务流程为后续软件设计打下建模基础。1. 航空订票系统为什么成了 UML 建模设计的经典练兵场如果你正在准备软考、做课程设计或者第一次接手一个业务逻辑复杂、状态流转多的系统大概率会碰到「航空订票系统」这个题目。它几乎是 UML 建模设计里出现频率最高的案例之一航班、舱位、座位、订单、支付、退改签每个实体都有自己的生命周期彼此之间又存在强关联。很多人一上来就打开工具画 uml 类图结果画到一半发现订单状态和座位状态对不上返工重来。这个标题真正要解决的问题不是「怎么画一张图」而是「怎么用一套 UML 视图把一个订票系统的静态结构和动态行为讲清楚并且让图能直接指导编码」。它适合三类人要交课程设计的学生、备考软考 UML 图试题的考生、以及需要给订票类业务做领域建模的初中级工程师。接下来我按实际建模顺序把每一步的产物、参数和踩过的坑讲透。2. 需求拆解与用例图先把「谁对系统做什么」钉死2.1 航空订票系统的四类参与者与核心用例建模第一步不是画类图而是确定参与者。航空订票系统里常见的参与者有四类旅客、票务代理、航司管理员、支付网关。旅客关心的是查询航班、预订、支付、退改签票务代理多一个批量出票航司管理员负责航班和舱位维护支付网关是外部系统参与者负责回调支付结果。用例图的价值在于划边界。我一般会先列一张参与者-用例对照表再动手画。表格比图更容易发现遗漏比如「候补购票」和「超售处理」这两个用例很多人的第一版都会漏掉。参与者核心用例触发方式旅客查询航班、预订座位、支付订单、退票、改签主动发起票务代理批量查询、批量出票、订单查询主动发起航司管理员航班维护、舱位维护、超售策略配置主动发起支付网关支付结果回调、退款回调外部触发用例之间的关系也要标清楚。include 用在「支付订单」必然包含「锁定座位」这种场景extend 用在「改签」可选触发「补差价」这种场景。很多人把 include 和 extend 画反导致后面时序图对不上。2.2 用例描述表怎么写才不会被推翻光画用例图不够每个核心用例要配一张用例描述表。以「预订座位」为例至少写清楚前置条件、主流程、异常流程、后置条件。主流程按「旅客提交查询 → 系统返回可用航班 → 旅客选择航班舱位 → 系统锁定座位 → 生成待支付订单」这样一步步写。异常流程是重点座位被并发抢占怎么办、支付超时怎么释放座位、航班取消怎么自动退款。这些异常流程直接决定了后面状态图和时序图的复杂度。我见过太多人用例描述只写主流程画状态图时才发现订单状态根本不够用。提示用例描述表里的异常流程条目建议直接编号后面画时序图时按编号逐条对应避免遗漏。3. 类图与领域模型把订单、座位、航班的关系理成可编码的结构3.1 识别实体类、边界类、控制类UML 类图在订票系统里通常分三层实体类Flight、Seat、Order、Passenger、Ticket、边界类BookingUI、PaymentGatewayAdapter、控制类BookingController、PaymentController。实体类承载业务数据边界类负责与外部交互控制类编排流程。实体类的属性要落到字段级别。比如 Order 类至少要有 orderId、passengerId、flightId、seatId、amount、status、createTime、expireTime。status 用枚举而不是字符串这一点在类图里就要标出来否则编码时容易写成魔法值。public class Order { private String orderId; private String passengerId; private String flightId; private String seatId; private BigDecimal amount; private OrderStatus status; // 枚举PENDING_PAY、PAID、CANCELLED、REFUNDED private Date createTime; private Date expireTime; // 支付超时时间用于释放座位 }这段代码对应类图里的 Order 实体。关键点是 status 用枚举、expireTime 单独存字段。expireTime 的作用是支撑「支付超时自动取消」这个用例如果类图里没这个字段后面定时任务就没有依据。3.2 类之间关系的多重性与聚合组合选择类图里最容易出错的是关系类型。Flight 和 Seat 是组合关系composition航班没了座位就没意义Order 和 Ticket 是聚合关系aggregation订单取消后票的历史记录可能还要保留Passenger 和 Order 是一对多关联。多重性也要标。一个 Flight 对应多个 Seat1..一个 Order 对应一个 Passenger1..1一个 Order 可以对应多个 Ticket1..比如多人同行。这些数字不是装饰它们直接决定数据库表的外键设计和 ORM 映射方式。public class Flight { private String flightNo; private ListSeat seats; // 组合关系级联删除 // getter/setter 省略 } public class Order { private Passenger passenger; // 1..1 private ListTicket tickets; // 1..* // getter/setter 省略 }组合关系在代码里体现为级联操作聚合关系则不级联。如果类图画错了编码时就会出现「删航班把历史订单也删了」这种事故。我一般会在类图旁边用注释标出级联策略方便后面 review。4. 时序图与状态图把订票、支付、退改签的动态行为画到能直接翻译成代码4.1 订票主流程时序图的对象与消息顺序时序图要画的是对象之间的消息传递顺序。订票主流程涉及的对象有旅客、BookingUI、BookingController、FlightService、SeatService、OrderService、PaymentGateway。消息顺序按「查询 → 选座 → 锁座 → 建单 → 支付 → 回调 → 出票」推进。画时序图时激活条activation bar要标清楚它表示对象在处理消息的时间段。锁座和建单这两步如果在同一个事务里激活条应该重叠。很多人画成串行导致后面实现时误以为可以分开提交结果出现「座位锁了但订单没建」的脏数据。旅客 - BookingUI: 提交选座请求 BookingUI - BookingController: bookSeat(flightId, seatId, passengerId) BookingController - SeatService: lockSeat(seatId) SeatService -- BookingController: lockResult BookingController - OrderService: createOrder(...) OrderService -- BookingController: orderId BookingController -- BookingUI: 返回待支付订单这段消息序列的关键在于 lockSeat 和 createOrder 的先后。必须先锁座再建单否则并发场景下会出现超卖。时序图里如果这两步之间没有事务边界标注实现时就要补上。4.2 订单状态图的状态、事件与守卫条件订单状态图是订票系统里最值得画的一张图。状态至少有待支付、已支付、已出票、已取消、已退款、改签中。事件包括支付成功、支付超时、用户取消、航班取消、改签申请、退款完成。守卫条件guard condition要写清楚。比如「待支付 → 已支付」的守卫是「支付回调金额等于订单金额」「待支付 → 已取消」的守卫是「当前时间超过 expireTime」。守卫条件不写状态图就退化成流程图失去了约束力。public OrderStatus transition(OrderStatus current, Event event, Order order) { switch (current) { case PENDING_PAY: if (event Event.PAY_SUCCESS order.getAmount().equals(order.getPaidAmount())) { return OrderStatus.PAID; } if (event Event.TIMEOUT new Date().after(order.getExpireTime())) { return OrderStatus.CANCELLED; } break; // 其他状态迁移省略 } throw new IllegalStateException(非法状态迁移); }这段代码是状态图的直接翻译。守卫条件体现在 if 判断里非法迁移直接抛异常。实际项目里我建议把状态迁移表单独抽出来配置化避免 switch 越写越长。5. 组件图与部署图从逻辑模型落到物理架构的最后一公里5.1 组件图里订票系统的分层与依赖方向组件图描述的是系统的物理模块划分。航空订票系统常见分层是表现层Web/App、应用层BookingService、PaymentService、领域层FlightDomain、OrderDomain、基础设施层DB、Cache、MQ、PaymentAdapter。依赖方向必须单向向下表现层依赖应用层应用层依赖领域层领域层依赖基础设施接口。如果组件图里出现领域层反向依赖应用层说明分层被破坏了。我一般会在组件图上用箭头明确标注依赖并在评审时专门检查有没有反向箭头。[Web UI] -- [BookingService] -- [OrderDomain] -- [OrderRepository] | | v v [PaymentService] [SeatDomain] | v [PaymentAdapter] -- [外部支付网关]这张组件图里PaymentAdapter 是基础设施层组件负责适配外部支付网关。它的存在让领域层不直接依赖外部系统方便测试时替换成 Mock。5.2 部署图与高并发场景下的节点规划部署图要标清楚节点和节点间的通信协议。订票系统的高并发压力集中在查询和锁座两个环节。常见部署方案是Web 节点集群 应用节点集群 Redis 集群缓存航班和座位状态 MySQL 主从 MQ 集群异步处理出票和通知。锁座操作建议放在 Redis 里用原子操作实现而不是直接压数据库。部署图里要体现 Redis 集群和应用节点的关系以及 MQ 在支付回调和出票之间的削峰作用。这些节点规划不是画着好看它直接决定后面容量评估和故障演练怎么做。注意部署图里的节点数量、规格不要照搬别人的方案要按自己的并发量估算。我一般按峰值 QPS 除以单节点承载能力再留 30% 余量。6. 避坑与排查UML 建模设计航空订票系统时最容易翻车的 5 个地方坑一用例粒度过粗后面所有图都对不上。现象是用例图里只有一个「管理订单」画时序图时发现根本没法拆消息。原因是需求阶段偷懒。解决办法是把每个用例拆到「一个参与者一次交互完成一个明确目标」的粒度比如「查询航班」「预订座位」「支付订单」分开。坑二类图里关系类型标错编码时级联删除失控。现象是删除航班后历史订单也被删了。原因是把聚合画成了组合。解决办法是重新审视生命周期座位随航班消亡订单不随航班消亡前者组合后者聚合。坑三时序图忽略事务边界并发场景超卖。现象是压测时同一座位被两个订单锁定。原因是锁座和建单之间没有事务或分布式锁。解决办法是在时序图上标注事务边界实现时用 Redis 原子操作或数据库行锁。坑四状态图守卫条件缺失非法状态迁移进数据库。现象是订单从「已取消」直接跳到「已出票」。原因是状态迁移没有守卫校验。解决办法是把状态迁移表配置化每次迁移前校验当前状态和事件是否匹配。坑五组件图依赖方向混乱领域层被外部系统污染。现象是单元测试没法跑因为领域层直接 new 了支付 SDK。原因是缺少适配器组件。解决办法是在组件图里补上 PaymentAdapter领域层只依赖接口。7. 用 PlantUML 把模型固化成可版本管理的文本画 UML 图最大的痛点是图文件和代码分离改一处要手动同步。我现在的习惯是用 PlantUML 把类图、时序图、状态图全部写成文本跟代码一起提交到 Git。这样每次需求变更改文本就能重新生成图diff 也看得清楚。startuml class Order { - String orderId - OrderStatus status - Date expireTime transition(Event): OrderStatus } class Seat { - String seatId - SeatStatus status } Flight 1 *-- many Seat : composition Order 1 o-- many Ticket : aggregation enduml这段 PlantUML 代码对应第 3 章的类图。关键点是组合用*--聚合用o--多重性写在引号里。生成图之后可以直接嵌到设计文档里评审时对着图讲比对着文字讲效率高得多。验证模型是否完整我一般用三个检查每个用例是否有时序图覆盖、每个实体是否有状态图如果它有状态、每个组件是否有明确的依赖方向。三个检查都过了模型基本就能直接指导编码。最后一个习惯模型评审时拉上后端和测试一起看测试能从状态图里直接推导出用例后端能从类图里直接推导出表结构这比事后返工便宜太多。希望帮到你。本文还有配套的精品资源点击获取