简介面向计算机、软件工程专业学生及课程设计/毕业设计写作者的机票预订系统详细设计报告聚焦详细设计阶段帮助读者解决系统分析、架构分层、数据库建模、模块划分与文档规范等撰写难题。压缩包内仅1个PDF文件大小约2.14MB可直接查阅、打印或作为电子模板使用。报告目录覆盖题目与问题定义、系统设计概述、可行性研究、需求分析、系统原理与主要技术、详细设计等章节并展开逻辑模型、业务流程图、软件结构、用户/航班/订单数据表、功能模块、安全性设计及测试计划内容偏重Web端B/S架构下的项目蓝图与设计文档表达。已有2824人学习下载适合用作课程设计参考、毕设模板、实验报告、项目立项及答辩材料也能辅助快速搭建机票预订系统的设计框架。1. 机票预订系统详细设计报告到底要交付到什么粒度在软件工程课程设计和毕设选题里机票预订系统常年排在最常见的题目当中真正卡住人的往往不是写代码而是把那份软件工程 机票预订系统 详细设计 报告.pdf写到合格线以上。常见做法是把概要设计里的模块框图复制一遍再补几句负责查询航班就交上去评阅人一眼就能看出问题没有算法、没有数据结构、没有接口签名、没有出错分支那是目录不是详细设计。详细设计的判定标准很朴素拿到这份文档的人能不能照着把代码写出来。所以必须出现的是模块划分依据、库表字段与约束、关键算法的输入输出与复杂度、接口函数签名、状态迁移和错误码。下面按实际做这类系统的顺序把模块结构、数据库、航班搜索算法、订单状态机和 PDF 成稿逐段说清代码以 Python 与标准 SQL 为主报告里可以直接改写成伪码或流程图。适合正在做软件工程课程设计、毕业设计或者第一次接手机票预订这类票务系统的后端同学。2. 从用例到模块机票预订系统的分层与接口契约详细设计的第一页通常不是类图而是模块边界。边界没锁死后面的表结构、接口和流程图都会互相打架写到最后只能靠其他模块兜底。2.1 用用例-模块映射表锁死模块边界把每一条用例落到唯一一个主责模块上是详细设计里最省事也最有效的约束。下面这张表可以直接放进报告模块名后面写代码时保持一致评审时也能看出职责没有重叠。用例归属模块输入输出关键约束查询航班flight_search出发地、目的地、日期、舱位可选航班列表含中转单次响应 800ms 内最多 3 段中转选择舱位占座inventory航班号、日期、舱位、人数锁定凭证 lock_id锁定 15 分钟到期自动释放提交订单orderlock_id、乘客信息、联系人订单号幂等超时回滚库存支付payment订单号、金额、支付渠道支付流水号支付窗口 15 分钟出票ticketing支付成功事件票号与航司接口对账失败可重推退改签order订单号、目标航班、乘客差额与手续费按退改规则阶梯计价判断粒度是否合理看两点一个用例是否只有一个主责模块跨模块调用是否全部走接口。如果某个模块既改订单表又直接读库存表说明边界划错了后面并发一上来必然出问题。2.2 分层包结构与依赖方向模块定下来之后用包结构把它固化。这里的核心是依赖单向下层不认识上层领域对象不依赖任何框架。flight_booking/ ├── api/ # 参数校验与响应组装不写业务分支 │ ├── flight_api.py │ └── order_api.py ├── service/ # 业务编排事务边界在这里 │ ├── search_service.py │ ├── inventory_service.py │ └── order_service.py ├── repository/ # 只放 SQL 与结果映射不做业务判断 │ ├── flight_repo.py │ └── order_repo.py ├── domain/ # 实体与值对象可被任意层引用 │ ├── flight.py │ └── order.py └── infra/ # 缓存、消息队列、支付网关适配 ├── cache.py └── pay_gateway.py依赖方向是 api → service → repository → domaininfra 横向被 service 依赖。写报告时把这段结构画成包图即可重点是标注repository 不允许被 api 层直接调用这类禁令评阅人看的就是这种约束。2.3 接口先写签名再写实现详细设计里的接口清单如果只有中文描述等于没写。用类型注解把签名固定下来参数含义和默认值一起交代清楚报告里可以原样贴成接口表。from dataclasses import dataclass from datetime import date, datetime from typing import Protocol, List, Optional dataclass(frozenTrue) class FlightQuery: dep_city: str # 出发城市三字码如 PEK arr_city: str # 到达城市三字码如 SHA dep_date: date # 出发日期按当地时区 cabin: str Y # 舱位Y 经济舱 / C 公务舱 / F 头等舱 max_stop: int 1 # 最大中转次数0 表示只看直飞 page_size: int 20 # 分页大小上限 100 dataclass(frozenTrue) class FlightOption: segments: List[str] # 航段列表直飞长度为 1 total_price: float # 含税总价单位元 depart_at: datetime arrive_at: datetime remain_seats: int # 该舱位剩余可售座位 class SearchService(Protocol): def search(self, q: FlightQuery) - List[FlightOption]: ... def lock_seat(self, flight_no: str, d: date, cabin: str, n: int) - Optional[str]: ...几个参数值得在报告里单独解释max_stop是后续是否调用中转路径算法的开关取 0 时只走直飞 SQLpage_size必须设上限否则一次把全部航班拉进内存排序接口会被拖垮cabin用单字母码而不是中文是为了和航司报文、库存表主键保持一致避免多一层映射。2.4 流程图写到能数出分支的粒度最常见的问题是流程图画成开始 → 查询 → 显示 → 结束四个框。详细设计的流程图应该能让人数出至少五条分支。以查询下单主链为例至少要覆盖参数校验失败返回错误码、直飞命中直接返回、直飞为空进入中转分支、占座时库存不足、锁定超时被释放后订单回滚。用绘图工具画完之后导出矢量图SVG 或 EMF再嵌进文档比截 PNG 更保险缩放和打印都不会发虚。流程里每个判断节点旁边标注对应模块和接口名这样图和 2.1 的映射表能互相印证不会出现流程里出现了表里没有的模块。3. 机票预订系统数据库详细设计表结构、索引与座位并发库表是详细设计里最容易被敷衍的部分也是唯一能直接转成建表语句的部分。写的时候按字段名、类型、约束、含义四列给全别只写中文名。3.1 六张核心表与字段取舍票务系统的表不用多但要够用。下面六张表能支撑查询、占座、下单、支付、出票、退改全流程。表名作用关键字段约束flight航班计划flight_no、dep_city、arr_city、dep_time、arr_time、aircraft主键 flight_no dep_datecabin_inventory舱位库存flight_no、dep_date、cabin、total_seats、sold_seats、version唯一键 flight_nodep_datecabinorder订单主表order_no、user_id、amount、status、request_id、expire_at唯一键 request_idpassenger订单乘客order_no、name、id_card、ticket_no外键 order_nopayment支付流水pay_no、order_no、channel、amount、status唯一键 pay_nosegment中转航段order_no、seq、flight_no、dep_date唯一键 order_noseq字段取舍上有两条经验库存不要用一条座位一行的建模几百万行座位记录会让查询和锁都变重总量 已售 版本号三列足够订单和乘客拆表是因为一个订单经常带多个乘客票号是乘客级别的混在一张表里退改签会很难写。3.2 建表 SQL 与索引选择CREATE TABLE cabin_inventory ( flight_no VARCHAR(10) NOT NULL, dep_date DATE NOT NULL, cabin CHAR(1) NOT NULL, total_seats INT NOT NULL DEFAULT 0, sold_seats INT NOT NULL DEFAULT 0, version INT NOT NULL DEFAULT 0, -- 乐观锁版本号 PRIMARY KEY (flight_no, dep_date, cabin) ) ENGINEInnoDB; CREATE TABLE order ( order_no VARCHAR(32) NOT NULL, user_id BIGINT NOT NULL, amount DECIMAL(10,2) NOT NULL, status TINYINT NOT NULL DEFAULT 0, -- 0 待支付 1 已支付 2 已出票 3 已取消 request_id VARCHAR(64) NOT NULL, -- 幂等键由客户端生成 expire_at DATETIME NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (order_no), UNIQUE KEY uk_request (request_id), KEY idx_user_status (user_id, status) ) ENGINEInnoDB;idx_user_status是给我的订单列表用的用户维度加状态过滤是最频繁的组合单独建索引比在 status 上建单列索引更实用。uk_request是幂等的基础重复提交会被数据库直接拦掉不用在应用层写分布式锁。注意amount用 DECIMAL 而不是 FLOAT金额比较和累加不会出现精度误差。3.3 座位库存扣减乐观锁还是悲观锁库存扣减是整个系统唯一真正需要认真对待并发的地方。选型结论通常是库存行冲突概率低时用条件更新乐观冲突高时用SELECT ... FOR UPDATE悲观。课程设计规模下条件更新足够逻辑也更清楚。-- 条件更新把判断和扣减合并成一条语句避免先查后改的竞态 UPDATE cabin_inventory SET sold_seats sold_seats :n, version version 1 WHERE flight_no :flight_no AND dep_date :dep_date AND cabin :cabin AND total_seats - sold_seats :n; -- 余量足够才更新def lock_seat(self, flight_no, d, cabin, n): # rowcount 为 0 表示余量不足或航班不存在统一按库存不足处理 rows self.db.execute(SQL_DEDUCT, {...}) if rows.rowcount 0: return None lock_id fLK{int(time.time() * 1000)}{random.randint(100, 999)} self.cache.setex(flock:{lock_id}, 900, json.dumps({ # 900 秒 15 分钟 flight_no: flight_no, dep_date: str(d), cabin: cabin, n: n })) return lock_id这里的关键点是判断余量和扣减在同一条 SQL 里完成WHERE里的余量条件保证了不会超卖返回的rowcount是唯一可靠的判断依据不要再用一条 SELECT 去确认。缓存里的锁凭证设 15 分钟过期配合一个定时任务扫描过期未支付的订单做回补把sold_seats减回去。参数上锁定时间不要超过支付窗口否则用户付完款座位已经释放出票会失败。3.4 幂等键与索引避免重复下单和慢查询幂等键由客户端生成服务端只做唯一约束校验。重复请求进来时捕获唯一键冲突直接查已有订单返回而不是重新走一遍占座流程。try: order_no self.order_repo.insert(request_idreq_id, ...) except DuplicateKeyError: order_no self.order_repo.find_by_request_id(req_id) # 幂等返回航班查询的索引要按实际 SQL 走。查询条件基本是出发城市 到达城市 日期因此flight表建联合索引idx_route_date (dep_city, arr_city, dep_date)把日期放在最后因为前两列的区分度更高。一个常见的误用是把dep_time也塞进联合索引实际查询按天过滤即可时间范围交给 SQL 的区间条件多一列反而增大索引体积。4. 航班搜索与中转路径算法Floyd、Dijkstra 怎么落地直飞查询是简单过滤真正能体现详细设计深度的是中转方案。中转本质上是图上的最短路径问题标题里出现类似 Floyd 的算法时多半就是要在这里做文章。4.1 直飞检索的 SQL 与排序权重SELECT f.flight_no, f.dep_time, f.arr_time, c.total_seats - c.sold_seats AS remain, p.price FROM flight f JOIN cabin_inventory c ON c.flight_no f.flight_no AND c.dep_date f.dep_date AND c.cabin :cabin JOIN price p ON p.flight_no f.flight_no AND p.cabin :cabin WHERE f.dep_city :dep AND f.arr_city :arr AND f.dep_date :d AND c.total_seats - c.sold_seats :n ORDER BY p.price ASC, f.dep_time ASC LIMIT :limit;排序权重建议做成配置而不是写死在 SQL 里价格优先、耗时优先、直飞优先三种策略对应不同的ORDER BY组合。如果后续要加综合排序把它拆成price * w1 duration * w2的表达式权重写进配置文件报告里也方便画成参数表。4.2 把中转航线建模成带权有向图中转查询的第一步是把航线网络抽象成图。城市是顶点直飞航段是边权重按你的优化目标定义。图元素对应实体说明顶点 V城市三字码如 PEK、CAN、URC边 E直飞航段有向PEK→CAN 与 CAN→PEK 是两条边边权 w价格 / 飞行时长 / 综合分决定最短路径的含义约束衔接时间、中转次数MIN_CONNECT 最短衔接时间MAX_STOP 最大中转次数路径一张联程票航段序列按时间递增排序建模时有两个容易忽略的点一是边权要能随日期变化同一航段不同日期的价格不一样所以图是按出发日期切片的不能建一张静态图缓存到底二是中转必须满足最短衔接时间国内航班常见 90 分钟否则算出来的路径在实际中根本接不上。4.3 Floyd 与 Dijkstra 的实现与选型Floyd 求全源最短路三重循环代码短适合城市数量有限的场景Dijkstra 求单源最短路配合优先队列适合只查某一个出发城市的场景。INF float(inf) def floyd(cities, w): w[i][j] 为 i 到 j 的直飞代价无边填 INF返回全源最短路矩阵 n len(cities) dist [row[:] for row in w] for k in range(n): # k 必须是最外层代表允许经过的中转点 for i in range(n): if dist[i][k] INF: continue # 剪枝不可达就不用继续内层循环 for j in range(n): if dist[i][k] dist[k][j] dist[i][j]: dist[i][j] dist[i][k] dist[k][j] return dist import heapq def dijkstra(graph, src): graph[u] [(v, cost), ...]返回 src 到各点的最小代价与前驱 dist {src: 0} prev {} pq [(0, src)] while pq: d, u heapq.heappop(pq) if d dist.get(u, INF): continue # 过期条目跳过 for v, cost in graph.get(u, []): nd d cost if nd dist.get(v, INF): dist[v], prev[v] nd, u heapq.heappush(pq, (nd, v)) return dist, prev两者的差别要在报告里写清楚Floyd 时间复杂度 O(n³)n 是城市数量几百个城市就是几千万次运算但一次算完可以回答任意城市对Dijkstra 用二叉堆是 O((VE)logV)只回答一个源点边权非负。课程设计的城市规模通常在几十到一百之间如果系统要提供任意两地中转方案的预计算Floyd 更省心如果只是用户查一次算一次Dijkstra 每次只跑相关子图响应更稳。边权含负值的情况在票价场景下不存在不需要考虑 Bellman-Ford。4.4 剪枝参数最短衔接时间与最大中转次数不做剪枝的最短路在中转场景下会算出实际不可售的路径比如凌晨落地、早上起飞、中间只有 40 分钟。下面这组参数建议在报告里单列一张表并且和实现里的常量一一对应。参数建议值作用MIN_CONNECT90 分钟国内中转最短衔接时间低于此值直接剪掉MAX_STOP2最大中转次数超过 3 段的路径不返回MAX_DETOUR1.8 倍总耗时不超过直飞的 1.8 倍避免绕远TOP_K20每个城市对最多保留 20 条候选路径再排序剪枝放在 Dijkstra 的松弛阶段做最自然在nd dist[v]判断之后再加一层衔接时间是否满足、段数是否超限、耗时是否超倍的检查不满足就不入堆。Floyd 版本则是先建图时就把不满足衔接时间的边权重设为 INF把约束前移到建图阶段代码更干净。注意中转次数统计的是边数减一写代码时容易和边数混淆测试时用一条两段中转的用例专门验证。5. 订单状态机、时序与异常分支的详细设计订单状态写不清后面所有异常处理都会变成 if-else 堆叠。详细设计里把它做成一张迁移表代码和测试用例都能从表里推出来。5.1 订单状态迁移表当前状态触发事件目标状态动作待支付(0)支付成功回调已支付(1)写支付流水通知出票待支付(0)超时未支付已取消(3)回补库存释放锁定待支付(0)用户取消已取消(3)回补库存已支付(1)出票成功已出票(2)写票号发通知已支付(1)出票失败已支付(1)记录重试次数进入重推队列已出票(2)退票申请已退款(4)计算手续费调用退款状态用整数存储注释里写明含义别用中文枚举存库。迁移表之外的状态组合一律视为非法更新时用条件更新兜底见 5.4。5.2 三段式下单时序与超时补偿下单不要在一个事务里做完所有事分成三段更符合实际先占座再创建订单最后等支付回调确认。def create_order(self, req_id, flight_info, passengers): cached self.cache.get(freq:{req_id}) # 1. 幂等前置检查 if cached: return json.loads(cached) lock_id self.inventory.lock_seat(**flight_info) # 2. 占座失败直接返回 if not lock_id: raise BizError(4001, 库存不足) order self.order_repo.create( # 3. 落单写库即幂等 request_idreq_id, status0, expire_atnow() timedelta(minutes15), passengerspassengers) self.cache.setex(freq:{req_id}, 3600, json.dumps(order)) self.mq.publish(order.created, order[order_no], delay900) # 4. 延迟消息兜底 return order延迟消息是这套流程里最关键的一环即使缓存回调和定时任务都失效15 分钟后延迟消息一定会到消费端检查订单状态仍是待支付就回补库存并把状态改成已取消。参数上expire_at和锁定时间、队列延迟时间三者必须一致改一个就要同时改另外两个报告里用一张对照表写清楚。5.3 错误码与异常分支设计错误码含义处理方式是否可重试4001库存不足提示换航班否4002锁定已过期重新占座是4003订单状态不允许该操作返回当前状态否5001支付网关超时查询订单状态勿盲目重发是查询优先5002出票接口失败进入重试队列最多 5 次是5003数据库唯一键冲突按幂等键返回已有订单是错误码要区分业务拒绝和系统故障两类前者直接返回给用户后者必须记日志并进重试队列。支付超时最忌讳直接重发扣款请求正确做法是先查询支付渠道的订单状态确认未支付再重新发起。5.4 幂等键与重试参数在报告里的写法所有状态更新都写成条件更新把允许的前置状态放进 WHERE让数据库来保证迁移合法。UPDATE order SET status 1, updated_at NOW() WHERE order_no :order_no AND status 0; -- 只允许从待支付迁到已支付重试参数集中成配置出票接口最多 5 次、间隔 30 秒指数退避、失败后进人工对账队列。这些数字不用精确但要在文档里给出理由比如航司接口平均恢复时间在 2 分钟内30 秒起步、5 次重试可覆盖约 8 分钟窗口。评阅人关心的不是数值本身而是你有没有想过失败之后会发生什么。6. 详细设计报告 PDF 成稿一致性自检与中文排版内容写完只是半程PDF 里图表编号错位、函数名和代码对不上、中文字体变成方框都会让整份详细设计掉档。这一节讲几个能立刻用上的处理方式。6.1 图表编号与交叉引用图和表用图 3-1 库存扣减流程这样的章节内编号正文里引用编号而不是如下图。这样中途插入一张图时只需局部重编不会全文乱套。函数名、表名、字段名在正文和代码里必须完全一致最省事的做法是把接口签名和建表 SQL 作为唯一来源正文只引用不重写。6.2 Markdown 导出中文 PDF 的可用命令如果正文用 Markdown 写导出时最容易踩的坑是中文缺字。用 pandoc 走 XeLaTeX 并指定中文字体基本能一次成功。# 导出中文 PDF必须用 xelatex并显式指定 CJK 字体 pandoc design.md \ -o 机票预订系统详细设计报告.pdf \ --pdf-enginexelatex \ -V CJKmainfontNoto Serif CJK SC \ -V geometry:margin2.5cm \ -V fontsize11pt \ --toc --toc-depth3 \ --number-sections几个参数的含义--pdf-enginexelatex是中文不出现方框的前提pdflatex 处理 CJK 要额外配置CJKmainfont换成系统里已安装的字体名Linux 上可以先fc-list :langzh查一遍--number-sections让章节号和正文的图 3-1能对上--toc-depth3保证目录细到三级标题。如果不用 pandocWord 里另存为 PDF 时记得在选项里勾选创建书签时使用标题否则目录点不动。6.3 交付前的自检清单检查项合格标准模块边界每个用例只有一个主责模块跨模块调用都有接口接口签名参数名、类型、默认值齐全与代码一致库表有主键、唯一键、索引说明字段类型明确并发库存扣减有明确方案超卖如何避免写得出来算法有输入输出、复杂度、剪枝参数能照着实现异常错误码表覆盖业务拒绝与系统故障两类排版图表编号连续目录层级正确中文无方框导出命令建议固化成脚本文档改一次就跑一次比手动另存为可靠得多脚本里加上字体检查那一行换机器时能立刻发现问题出在字体而不是内容。本文还有配套的精品资源点击获取