机票预订系统课程设计:瀑布式六文档与 C# WinForms 数据库实现
2026/9/17 17:49:12 网站建设 项目流程

简介:这是一份软件工程课程设计报告模板,主题为飞机售票(机票预定)系统,面向高校软件工程、计算机相关专业的学生与课程指导教师。报告按瀑布模型完整覆盖软件生存周期各环节:项目开发计划书、需求规格说明书、设计规格说明书、源程序清单、测试报告与用户手册六大部分,包含术语定义、相关文档、任务与工作产品划分、工作量与规模估算、资源需求计划及进度计划表等规范内容;需求部分给出查询、预定、取消预定等场景描述与用例分析,还整理了性能测试、虚拟用户、响应时间、吞吐量、并发用户数等术语,并列出 Word2007、Visual Studio 2008、Rational Rose、SQL Server 2005 等开发与测试环境配置,便于读者对照撰写与答辩准备。资源包内共1个doc文档,约432KB,共52页,章节结构清晰、格式规范,可直接作为课程设计报告的参考范文或改写底稿。目前已有1841人学习下载,适合需要快速搭建报告框架、补充需求分析与测试环节内容的学习者参考借鉴。

1. 机票预订系统课程设计报告:六份文档串起一条瀑布式开发链路

课程设计截止前一周,多数人手里只有几段能跑的界面代码,缺的是把需求、设计、编码、测试串成一条线的那份文档。这份52页的机票预订系统课程设计报告,把项目开发计划书、需求规格说明书、设计规格说明书、源程序清单、测试报告、用户手册六份材料装进同一份 Word 文档,对应瀑布模型从需求分析到运行维护的完整链路。系统本身不复杂:旅客查航班、旅行社订票并打印取票通知和账单、起飞前24小时取票、退票按规则扣手续费,但每个环节都要落到用例、类、表和代码上。

技术栈是当年课程设计的主流组合——Visual Studio 2008 写 C# WinForms 客户端,SQL Server 2005 存航班与订单,Rational Rose 画用例图和类图。它的价值不在代码量,而在文档之间的对应关系:需求里的一个用例,能在设计里找到对应的类和表,在源程序里找到对应的事件处理函数,在测试报告里找到对应的用例编号。

建议按文档编号通读一遍,重点盯住需求用例表、模块类清单和数据字典三处。

2. 需求规格说明书的拆解:从场景描述到功能需求点列表

2.1 场景描述为什么要写成对话剧本

报告里最不像文档的部分就是场景描述:售票员A和旅客B一问一答,从"我想订一张成人的票"一直写到"用我的 VISA 卡,卡号是1111-2222-3333-4444"。这种写法在评审时容易被批太啰嗦,但它解决的是需求分析里最难的一环——把业务人员脑子里的隐性流程变成可编号的动作序列。预定场景里能数出九步:点击查询、输入航班信息、系统列出符合条件航班、点击新建订单、录入旅客证件信息、填写联系方式、确认付款、系统返回银行验证结果、打印账单和取票通知单。每一步往前推是一个界面状态,往后退是一条异常分支。

写这类场景有三条经验。第一,一次只写一个参与者的视角,别在旅客的动作里混进数据库写入动作;第二,每个动作后面必须跟一句系统响应,否则后面对应画序列图时找不到返回消息;第三,把"没有找到符合条件的航班""账单号错误"这类可选事件流单独拎出来写,不要塞在主流程中间。软件工程课程设计里最常被扣分的地方,就是主流程和异常流程糊成一段,后面测试用例根本没法从需求里长出来。

2.2 用例表的要素与事件流边界

五个核心用例的写法定下来之后,评审时最容易被追问的是"触发条件写了没有""后置条件写的是业务还是数据"。用一张对照表把边界钉住:

用例主要参与者触发条件主事件流关键节点可选事件流
查询航班旅客无(主界面已打开)输入目的地/时间/航班号→系统查询→列表显示无符合条件航班
机票预定旅行社、机场售票员旅客来电或到柜台查询→新建订单→填写旅客信息→确认付款→打印账单无符合航班,询问是否改签
取消预定机场售票员旅客来电要求取消输入订单号→显示账单→确认取消→退还订金→删除账单未找到订单,返回待输入状态
退票机场售票员旅客持票到柜台输入票号→核对身份→计算手续费→退款→机票状态改待售查不到票号,提示核对真伪
取票机场售票员旅客到场取票输入账单号→核对24小时限制→打印机票→状态改已售出账单错误,返回待输入

前置条件写系统状态,比如"机票预定系统主界面已经打开";后置条件写数据状态变化,比如"可订机票减少、定金总量增加、数据库更新"。主事件流编号要和2.1里的场景步骤对得上,否则两处描述会互相打架。表里"核对24小时限制"这一条,就是后面取票校验代码的落点,需求阶段埋下的每一个判断,后面都得在代码里还债。

2.3 功能需求点列表与性能需求点列表怎么填

功能需求点列表一般六列:编号、功能名称、使用人、功能描述、输入内容、输出内容。填一条示例:编号2,功能名称"预定机票",使用人"旅行社",输入"预定的机票号、用户信息",输出"预定完成通知"。这张表最大的用处不是给人看,而是给后面的模块清单和测试用例提供锚点——功能点编号和测试用例编号最好成对出现,答辩时一眼就能看出哪个功能压根没测。

性能需求点列表要落到可量化的数字上。报告里的时间要求是查询最长等待0.5分钟、记账处理最长1分钟、远程数据传输1分钟;空间要求是支持终端数1、不支持并行操作、处理的文件和记录数4、处理任务数15、输入输出精度为双精度、中间过程小数点后保留4位。这些数字看着随意,但它们是测试报告里"系统满足性能需求"这句话唯一的依据。改需求文档时顺手把数字也改了,测试报告就对不上了。

2.4 把用例表落成可校验的结构化数据

Word 表格改起来费劲,字段还容易漏。我一般先把用例写成结构化文本,再用脚本生成文档表格,改一处就能全量重排。

{ "use_case_id": "UC-02", "name": "机票预定", "primary_actor": "旅行社", "secondary_actor": ["旅客", "机场售票员"], "precondition": "系统处于主界面,售票人员等待旅客", "trigger": "旅客提出订票需求", "main_flow": [ "售票员进入查询界面并输入航班信息", "系统返回符合条件的航班列表", "售票员点击新建订单", "录入旅客姓名、证件类型、证件号、证件地址", "录入联系人手机、固定电话、Email", "选择支付方式并提交支付", "系统等待银行返回验证结果", "打印账单与取票通知单" ], "alt_flow": [ {"step": 2, "condition": "无符合条件航班", "action": "提示无航班并询问是否改签"} ], "postcondition": "订单写入数据库,可订座位数减少,定金金额增加", "related_module": "M-01 旅行社系统", "test_case_id": "TC-02" }

字段说明:use_case_idtest_case_id成对出现,是需求到测试的追溯线;related_module指向设计规格说明书里的模块编号;main_flow的数组下标要和 Word 版用例表里的步骤编号一致,改编号时两边同步。用 python-docx 读这个文件批量生成表格,比手工敲六张用例表省事得多,字段也不会今天漏一个明天补一个。

3. 设计规格说明书的落地:类图、模块清单与四张核心数据表

3.1 从用例到类:边界类、控制类、实体类的划分

用例表里的名词就是候选类。参与者一侧有旅客、旅行社、机场售票人员、机场管理人员;实体一侧有航班、机票、客户订单、账单、取票通知单。WinForms 客户端里,每个窗体天然是一个边界类,主界面 Form1 承担控制类入口的角色,把用户动作分派到旅行社系统、售票员系统、旅客查询系统三个模块。

划分时最容易犯的错是把"打印账单"当成一个类。打印是动作不是名词,它要么是账单类上的方法,要么是一个独立的打印服务类。判断标准很直接:能存进数据库的当实体,出现在界面上的当边界,协调多个实体完成一次用例的当控制。按这条线切下来,类图上的关系基本就是一对多和聚合,不会出现一团乱麻的双向关联。

3.2 模块(类)清单与命名规则

模块清单四列:编号、模块英文名、功能简述、接口简述。

编号模块(类)英文名功能简述接口简述
M-01AgencyModule预定、收费、打印通知单与账单BookTicket(orderId)、PrintBill(billId)
M-02CounterModule打印机票、退订金、退票Refund(ticketNo)、IssueTicket(billNo)
M-03QueryModule航班查询、交费查询SearchFlight(toCity, date, flightNo)

命名规则在报告1.2节里只写了"申明全局变量、局部变量对象的命名规则",实际要写死才管用。常见做法是:全局变量加g_前缀,窗体控件沿用 WinForms 默认名(btnQuery、dgvFlight),数据库表名用T_前缀加下划线,字段名小驼峰,视图加V_。规则一旦写进文档,后面接手改代码的人就不会各写一套,源程序清单里的函数名也能和模块清单对得上号。

3.3 四张核心表与字段类型设计

报告里用框图列了账单、取票通知单、机票、客户订单四组数据,落到 SQL Server 2005 大致是这个样子。

-- 航班表:查询与余座的基础 CREATE TABLE T_Flight ( FlightNo VARCHAR(10) NOT NULL PRIMARY KEY, FromCity NVARCHAR(20) NOT NULL, ToCity NVARCHAR(20) NOT NULL, DepartTime DATETIME NOT NULL, ArriveTime DATETIME NOT NULL, SeatTotal INT NOT NULL DEFAULT 0, SeatLeft INT NOT NULL DEFAULT 0, Price DECIMAL(18,4) NOT NULL ); -- 机票表:取票后状态改已售出,退票后改待售 CREATE TABLE T_Ticket ( TicketNo VARCHAR(20) NOT NULL PRIMARY KEY, FlightNo VARCHAR(10) NOT NULL, SeatNo VARCHAR(5) NULL, TicketDate DATETIME NOT NULL, Status TINYINT NOT NULL DEFAULT 0 -- 0待售 1已预定 2已售出 3已退 ); -- 订单与账单:票价、订金、手续费分开存,退票时才算差额 CREATE TABLE T_Order ( OrderId VARCHAR(20) NOT NULL PRIMARY KEY, CustomerName NVARCHAR(30) NOT NULL, IdCard VARCHAR(20) NOT NULL, CardNo VARCHAR(24) NULL, TicketNo VARCHAR(20) NULL, CreateTime DATETIME NOT NULL DEFAULT GETDATE() ); CREATE TABLE T_Bill ( BillNo VARCHAR(20) NOT NULL PRIMARY KEY, OrderId VARCHAR(20) NOT NULL, Amount DECIMAL(18,4) NOT NULL, -- 票价 Deposit DECIMAL(18,4) NOT NULL, -- 订金 Fee DECIMAL(18,4) NOT NULL DEFAULT 0, -- 手续费 PrintTime DATETIME NULL, ExpireTime DATETIME NULL -- 取票截止时间 );

字段说明:SeatLeft必须在订票成功和退票成功时同步加减,它是查询界面显示余座的唯一来源;Status用 tinyint 枚举而不是中文串,否则打印和统计时到处做转换;金额统一 DECIMAL(18,4),对应性能需求里"小数点后4位",别用 float,手续费累加会出现分位误差;ExpireTime存取票截止时间,取票界面拿它和当前时间比较,判断是否还在起飞前24小时之外。数据库课程设计里这四张表就是数据字典的主体,字段注释一行都别省。

3.4 接口设计:打印接口与银行结算接口

接口表要写清七个字段:接口名称、接口内容、接口设施、接口数据结构、传输速率、带宽、协议。

接口名称接口内容设施数据结构协议
打印接口输出机票、账单、取票通知单本地打印机文本流 + 版式模板本地调用,无网络协议
银行结算接口扣款、退款、验证结果返回银行中间件请求:卡号、金额、订单号;响应:返回码、流水号按银行提供的规范

打印接口在 WinForms 里通常封装成 PrintService,入参是账单号,内部按模板填充字段;银行结算接口在课程设计阶段一般用模拟函数代替,但要保留返回码字段,测试报告里才有"支付失败"这条异常分支可写。接口签名一旦确定,源程序清单里的函数名和参数顺序就不能再随便动,否则设计文档和代码两张皮。

4. WinForms 源程序实现:Form1 主控界面、ADO.NET 参数化查询与退票事务

4.1 Form1 主控界面与窗体间数据传递

源程序清单里给出的是 WinForms 设计器自动生成的部分类,包含 components 字段、Dispose 重写和 InitializeComponent 框架。这段代码本身没什么可改的,重点在主界面按钮与子窗体的对应关系。

控件功能对应模块涉及表
btnQuery查询航班M-03T_Flight
btnBook新建订单并付款M-01T_Order、T_Bill、T_Ticket
btnCancel取消预定M-01T_Order、T_Bill
btnRefund退票M-02T_Ticket、T_Bill
btnIssue取票并打印机票M-02T_Bill、T_Ticket

窗体之间传订单号或票号有两条路:构造函数注入,比如new FormRefund(ticketNo);或者做一个静态会话类 CurrentSession,保存当前售票员工号、当前订单号、当前账单号。课程设计里窗体数量不多,构造函数注入更直观,也方便以后单测。主界面在子窗体关闭后要刷新余座列表,否则退完票回来看到的还是旧数据,答辩时很容易被当场点破。

4.2 ADO.NET 参数化查询封装

查询用例有三个可选条件:目的地、起飞时间、航班号。WHERE 子句动态拼接最容易写出注入漏洞,正确做法是先把条件拼成参数占位符,再统一绑定参数值。

public DataTable SearchFlight(string toCity, DateTime? departDate, string flightNo) { var sql = new StringBuilder( "SELECT FlightNo, FromCity, ToCity, DepartTime, SeatLeft, Price FROM T_Flight WHERE 1=1 "); var cmd = new SqlCommand(); if (!string.IsNullOrEmpty(toCity)) { sql.Append(" AND ToCity = @ToCity "); cmd.Parameters.Add("@ToCity", SqlDbType.NVarChar, 20).Value = toCity; } if (departDate.HasValue) { // 只比较日期部分,避免时分秒把当天航班过滤掉 sql.Append(" AND CONVERT(date, DepartTime) = @DepartDate "); cmd.Parameters.Add("@DepartDate", SqlDbType.Date).Value = departDate.Value.Date; } if (!string.IsNullOrEmpty(flightNo)) { sql.Append(" AND FlightNo = @FlightNo "); cmd.Parameters.Add("@FlightNo", SqlDbType.VarChar, 10).Value = flightNo; } cmd.CommandText = sql.ToString(); using (var conn = new SqlConnection( ConfigurationManager.ConnectionStrings["TicketDb"].ConnectionString)) using (var da = new SqlDataAdapter(cmd)) { cmd.Connection = conn; var dt = new DataTable(); da.Fill(dt); // DataAdapter 自动 Open/Close 连接 return dt; } }

代码逻辑:先拼一个恒真条件1=1,再按用户实际填了哪些字段追加 AND 子句,每个子句对应一个 SqlParameter,值永远不拼进 SQL 文本。参数说明:@ToCity用 NVarChar,因为目的地是中文城市名;@DepartDate用 date 类型并取.Date,绕开 datetime 的时分秒精度问题;连接字符串放在 App.config 的 connectionStrings 节点,换库时不用重新编译。返回 DataTable 直接绑到 DataGridView,比逐行读 DataReader 少写一层循环。

注意:SqlDataAdapter.Fill 会自动开关连接,但 SqlCommand 必须挂到同一个 SqlConnection 上,漏掉cmd.Connection = conn会报"未将对象引用设置到对象的实例"。

4.3 退票手续费计算与退款事务

退票用例里有三个动作必须一起成功:机票状态改为待售、写入退款流水、账单标记已退。任何一步失败都会留下"钱退了票还在"的脏数据,所以用事务包起来。手续费阶梯常见做法是按离起飞时间分档:

离起飞时间手续费比例说明
起飞前24小时内票价的20%与取票时间同一临界点
24至72小时票价的10%中间档
72小时以上票价的5%最低档,建议设最低收费金额
public decimal Refund(string ticketNo) { using (var conn = new SqlConnection(connStr)) { conn.Open(); using (var tran = conn.BeginTransaction()) { try { // 1. 取票面信息,加更新锁,避免同一张票被两个窗口同时退 var cmd = new SqlCommand( "SELECT t.Status, f.Price, f.DepartTime " + "FROM T_Ticket t WITH (UPDLOCK) " + "JOIN T_Flight f ON t.FlightNo = f.FlightNo " + "WHERE t.TicketNo = @TicketNo", conn, tran); cmd.Parameters.AddWithValue("@TicketNo", ticketNo); // ... 读取 Status / Price / DepartTime if (status != 2) throw new InvalidOperationException("该票未售出,不能退票"); decimal fee = CalcFee(price, departTime, DateTime.Now); // 按阶梯表计算 decimal refund = price - fee; // 2. 机票状态回到待售,座位还回去 new SqlCommand("UPDATE T_Ticket SET Status = 0 WHERE TicketNo = @TicketNo", conn, tran).ExecuteNonQuery(); // 3. 手续费与退款金额写回账单,4. 写退款流水 // ... tran.Commit(); return refund; } catch { tran.Rollback(); // 任一步失败,状态与金额一起回滚 throw; } } } }

参数说明:UPDLOCK提示在同一事务里对机票行加更新锁,退票按钮被重复点击时第二次会阻塞或报错,而不是把同一张票退两次;CalcFee接收票价、起飞时间、当前时间三个入参,阶梯规则改了只动这一个函数;Rollback()放在 catch 里并把异常抛给界面层弹提示。起飞前24小时这个临界点被取票和退票共用,建议抽成一个 TimeSpan 常量,别在两处各写一遍。

4.4 打印机票与取票通知单的技术要点

打印走 System.Drawing.Printing 里的 PrintDocument,在 PrintPage 事件里用 Graphics.DrawString 按坐标画字段,再挂到 PrintPreviewDialog 上预览。课程设计里字段固定,直接算坐标比上模板引擎省事;账单和取票通知单共用一份打印代码,用一个版式参数区分标题和字段清单即可。取票前的校验顺序别写反:先用账单号查到记录,再判断当前时间是否早于 ExpireTime,超过就提示"账单已过期",最后才改机票状态为已售出并调打印。顺序反了会出现"票打了但状态没改"或者"状态改了但没打出来"两种情况,退票时对不上账。

5. 测试报告与验收:响应时间口径、边界用例和自查清单

5.1 响应时间与并发指标怎么测才有说服力

需求里写的是查询最长等待0.5分钟、记账1分钟,测试报告就得给实测数字。做法是在按钮事件外面套一个 Stopwatch,连续跑20次取平均值和最大值,并记录数据量,否则"耗时35毫秒"这种结论别人无法复现。

var sw = Stopwatch.StartNew(); var dt = svc.SearchFlight("北京", DateTime.Today, null); sw.Stop(); Console.WriteLine($"返回 {dt.Rows.Count} 行,耗时 {sw.ElapsedMilliseconds} ms");

并发方面,需求写明不支持并行操作、支持终端数1,所以不必做压测,但要验证同一订单被两个窗口同时打开时的冲突:开两个退票窗口输入同一个票号,第二个应该被行锁挡住或提示已处理。这类记录写进测试报告,比一句"系统运行稳定"有分量得多,也正好复用4.3节里 UPDLOCK 的设计意图。

5.2 边界用例清单与答辩自查

用例编号场景预期结果
TC-05-01查询条件全为空返回全部航班或提示补充条件,不报错
TC-05-02起飞时间刚好在24小时后允许取票,手续费按24小时档计
TC-05-03账单号不存在提示"账单错误",返回待输入状态
TC-05-04同一票号重复退票第二次提示该票未售出
TC-05-05证件号超长输入框截断或提示格式错误
TC-05-06手续费高于订金退款金额为0,不允许负数入账

答辩前对着这张表和需求里的功能需求点列表逐个打勾,凡是功能点列表里有编号、测试表里没有对应行的,就是缺口。填"预期结果"时写成可观察的现象——提示什么文字、状态变成哪个值、哪张表多了一行——评委照着点一遍就能复现,这份测试报告才算立住。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询