简介:适合软件工程课程设计使用的医院在线预约系统完整报告,面向计算机相关专业学生及需要完成类似课题的开发者,用于理解需求分析、结构化设计与面向对象设计的全流程。资源为一份 Word 文档,压缩包内共 1 个 doc 文件,整体大小 762KB;内容按设计目的、可行性分析、需求分析、结构化设计、面向对象设计、测试方案等章节展开,覆盖系统流程图、数据流图、数据字典、E-R图、软件结构图、Jackson图、用例图、类图和顺序图等关键设计产物,可作为课程设计报告撰写与系统设计的直接参考。目前已有1014人学习下载。通过这份文档,读者可以掌握从可行性分析到测试方案的软件工程规范步骤,并借鉴医院预约场景下的模块划分、数据结构设计和接口表达方式,节省从头搭建报告框架的时间,提升课程设计完成质量。
1. 一次挂号的业务只有几步,文档的检查链路却超过十张图
一个“医院在线预约”业务,落地到数据库就是一条 insert:病人选科室、选医生,生成挂号单,收费闭环。但放到软件工程课程设计里,同样的动作要拆成五层文档:可行性分析要有系统流程图、数据流图(DFD)和数据字典,需求分析要有 E-R 图,结构化设计要有软件结构图和 Jackson 图,面向对象设计要补用例图、类图、顺序图和状态图,最后还必须有一份测试方案。真正让课设难产的通常不是画图本身,而是同一份数据在系统流程图里叫“病人姓名”,到数据字典里叫 name,到类图里变成 patientName,三张图对不上,答辩时每个不一致都会被翻出来。这篇文章以医院在线预约系统课程设计为主线,从可行性分析、需求建模、结构化设计,到面向对象设计和测试方案逐层拆开,每一步都给出能直接抄的步骤、参数和自查方法。
2. 可行性分析三件套:系统流程图、数据流图和数据字典的对齐方法
2.1 系统流程图与 DFD:物理视图和逻辑视图必须分开画
课设原文里,系统流程图画的是挂号全过程:开始,输入病人姓名、性别、编号,确认挂号后开启新的挂号单界面初始化资料,再判断专家是否有时间;有时间则执行挂号并缴纳专家费用,没时间则改天再来或转普通会诊,最后退出挂号。这一层的重点是物理动作:谁在哪个界面操作、分支怎么走,用 StarUML 的基本流程图就能画完,不需要引入数据流专用符号。
DFD 则必须与系统流程图分开画,它属于逻辑模型,不关心界面,只关心数据从哪里来到哪里去。课程设计报告里常见的第一个扣分点是 DFD 只画一层,直接从一个整体框跳进细节,缺少上下文图。上下文图也叫 DFD 第 0 层,规则很简单:整个系统画成一个加工,病人、工作人员、医生作为外部实体放在图外,外部实体与系统之间的数据流标清楚。这一层画的不是功能,而是系统边界——哪些数据进、哪些数据出,评审第一眼看的就是这个。
画完第 0 层,再拆第 1 层。“挂号系统”这个加工通常拆成下面 4 个加工:
| 加工编号 | 加工名称 | 主要输入 | 主要输出 |
|---|---|---|---|
| 1.1 | 接收病人信息 | 病人姓名、性别、年龄、病历号 | 格式化病人记录 |
| 1.2 | 生成挂号单 | 病人记录、科室、医生信息 | 挂号单草稿 |
| 1.3 | 收费处理 | 挂号单草稿、费用标准 | 缴费结果 |
| 1.4 | 打印挂号单 | 缴费结果 | 挂号单凭证 |
这里必须做一次对齐检查:DFD 第 0 层与第 1 层的数据流名称一致。比如第 1 层加工 1.4 输出“挂号单凭证”,在第 0 层里就应该有同名输出流流向病人;如果第 0 层写的是“预约信息”,而第 1 层所有加工里都没有这个名字的数据流,说明需求阶段漏了“预约记录”这个数据存储,后续画 E-R 图时就会少一张表。
2.2 数据字典:用脚本生成条目,不靠 Word 里手敲
数据字典是为 DFD 服务的词典,DFD 里出现的每个数据流、数据存储和加工,都应该在数据字典里找到对应定义。课设原文给的三个典型条目很能说明问题:
- Shu-004:年龄,字符型,3 位,范围 0~999;
- Shu-005:科室,字符型,4 位;
- Shu-006:挂号类型,字符型,2 位,取值普通、专家、主任。
但数据项分散写在文档里,很容易出现同一个“科室”在结构化设计里变成 8 位,在类图里又变成 varchar(20)。我一般会建议用一段小脚本管理这些条目,而不是在 Word 里手动维护。下面这段 Python 能按统一模板输出条目,改起来也快。
# data_dict_gen.py # 把数据项定义集中在一个列表里,按统一模板输出字典条目 entries = [ {"id": "Shu-004", "name": "年龄", "alias": "", "type": "字符型", "width": 3, "range": "0~999"}, {"id": "Shu-005", "name": "科室", "alias": "", "type": "字符", "width": 4, "range": "内科,外科,儿科,妇产科"}, {"id": "Shu-006", "name": "挂号类型", "alias": "", "type": "字符", "width": 2, "range": "普通,专家,主任"}, ] for entry in entries: print("[{}] {}:类型 {}, 宽度 {}, 取值范围 {}".format( entry["id"], entry["name"], entry["type"], entry["width"], entry["range"] ))参数说明:id是数据项编号,对应报告中的 Shu-XXX;name是对外名称,必须与 DFD 数据流名或数据存储字段同名;alias留空时也照常输出,保证格式统一;width用来和数据库字段长度对照。脚本跑完,把输出与 DFD 的数据流名做一次集合比对,缺失和改名的情况立刻暴露。
提示:如果学校要求“数据字典用 Excel 写,插入——对象——由文件创建”,不要把控制台纯文本直接粘进 Word。先导出 .xlsx 再用 Word 的对象功能嵌入,否则评阅老师看到的是一段没有表格结构的字符串。
2.3 E-R 图:实体收敛到四个,联系只保留三条主线
E-R 图的作用是确定数据存储层。医院挂号系统最怕把实体画成一团:病人、医生、科室、挂号单、费用、排班、病历全揉在一起。一个课设周期的合理做法是先收敛实体,围绕“挂号”这个核心业务保留四个实体:
| 实体 | 关键属性 |
|---|---|
| 病人 | 病人编号、姓名、性别、年龄、病历号 |
| 科室 | 科室编号、科室名称、楼层位置 |
| 医生 | 医生编号、姓名、职称、科室编号 |
| 挂号单 | 挂号单编号、挂号日期、挂号类型、病人编号、医生编号、费用状态 |
联系方面只保留三组 1:N 关系:病人与挂号单是 1:N,医生与挂号单是 1:N,科室与医生是 1:N。如果出现 M:N 关系,比如一个专家在多个科室出诊,就要拆出关联实体,课设周期内一般不必进入这一步。E-R 图的正确用法是直接支撑后面的库表设计:挂号单表以病人编号和医生编号作为外键,挂号类型字段与数据字典 Shu-006 的取值范围保持一致。属性个数太多时,把键属性放在实体框左上角,非键属性按业务分组,避免画到两页 A4 纸还放不下。
3. 结构化设计:从数据流图到模块与过程的两次映射
3.1 软件结构图:把 DFD 的加工节点改造成调用层次
结构化设计的核心工作是把 DFD 转换成软件结构图,这一步在课程设计中通常叫变换分析。做法是先识别 DFD 里的传入路径、变换中心和传出路径,再把这三段映射成结构图中的输入模块、中心转换模块和输出模块。
以第 1 层 DFD 为例,加工 1.1“接收病人信息”是传入路径,1.2“生成挂号单”是变换中心,1.3“收费处理”和 1.4“打印挂号单”是传出路径。转换后的软件结构图顶层是“挂号系统”,第二层分别是:
| 模块编号 | 模块名 | 职责 | 对应 DFD 加工 |
|---|---|---|---|
| M-1 | 获取病人信息 | 接受输入并格式校验 | 1.1 |
| M-2 | 构造挂号单 | 组装病人与医生数据 | 1.2 |
| M-3 | 计费处理 | 按挂号类型计算费用 | 1.3 |
| M-4 | 输出挂号单 | 生成打印凭证 | 1.4 |
| M-5 | 存储预约记录 | 写病人信息与预约信息 | 数据存储 D1、D2 |
这里要强调一个常见的判断错误:软件结构图不是把 DFD 原样换个方向放,而是要把“数据流”变成“调用关系”。DFD 里的箭头表示数据流动,结构图里的箭头表示模块调用和返回。模块划分之后还需要用内聚和耦合检查一遍:M-3 计费处理的内聚类型应该是功能内聚,因为它只做一件事;如果 M-3 里同时塞了“判断专家是否有时间”的逻辑,那它就是逻辑内聚,答辩时评审会直接点出来。
3.2 Jackson 图:用顺序、选择、重复描述挂号单的数据结构
Jackson 图的目的是让程序结构直接映照数据结构,核心只有三种结构:顺序、选择、重复。课程设计报告里出现 Jackson 图时,常见的错误是画成普通的树状图,没有区分这三种图元。
以挂号单的数据结构为例:
- 顺序:一张挂号单 = 挂号单标识 + 病人信息 + 挂号信息;
- 选择:挂号类型是普通、专家或主任中的一种;
- 重复:一个医生名下挂着多张挂号单。
顺序结构用直线依次排列,选择结构用右上角画圈的判断节点,重复结构用右上角画星号表示循环。Jackson 图的价值在于它能直接指导程序写法。比如按下面这段 JavaScript 骨架,就能把三种结构对应到代码路径。
// jackson_register.js // 与 Jackson 图对应的挂号单生成逻辑骨架 function createTicket(patient, doctor, type) { const ticket = { id: generateId(), date: new Date() }; // 顺序:单标识 // 选择:按挂号类型确定费用路径 if (type === '专家') { ticket.fee = 50; } else if (type === '主任') { ticket.fee = 80; } else { ticket.fee = 10; // 普通 } // 重复:把新挂号单加入医生名下的列表 doctor.tickets.push(ticket); ticket.doctorId = doctor.doctorId; return ticket; }逻辑说明:ticket先被赋予编号和日期,完成“顺序”中的挂号单标识;随后if/else结构对应 Jackson 图的“选择”,每个分支设置不同费用;最后push动作对应“重复”,把当前单挂载到医生列表尾部。参数type对应数据字典 Shu-006 的取值,如果传入的字符串不在“普通、专家、主任”范围内,调用方应该在进入createTicket之前拦截,而不是在这里兜底,不然 Jackson 图上的三个分支就覆盖不到异常路径了。
4. 面向对象设计:用例图、类图、顺序图与状态图的闭环
4.1 用例图:参与者画错,后面全是白做
用例图是面向对象设计的入口,它回答“系统为谁提供什么价值”。医院在线预约系统里,参与者至少有三个:病人、工作人员、医生。课设原文用例图里出现的“登录系统、挂号系统、挂号单结束并退出、输入病人姓名性别、输入科别信息、输入医生信息、收取挂号费、保存信息、产生挂号单、打开新界面”这些用例,需要按参与者归组:
| 参与者 | 用例 | 前置条件 | 主事件流 |
|---|---|---|---|
| 病人 | 创建挂号单 | 选择科室和医生 | 提交基本信息,生成挂号单 |
| 工作人员 | 登记病人信息 | 已登录系统 | 输入姓名、性别、病历号并保存 |
| 工作人员 | 收取挂号费 | 挂号单已生成 | 按类型计费并更新费用状态 |
| 医生 | 查看当日挂号 | 已登录系统 | 查询科室名下挂号单列表 |
画用例图时要注意两个点。第一,用例名必须是动词短语,不能写“挂号单”三个字,要写“创建挂号单”或“打印挂号单”;第二,参与者之间的关系用泛化,比如“收费员”泛化自“工作人员”,不要画成两个孤立角色同时连接同一批用例,那样用例图会被拉宽且没有信息量。
4.2 类图:从用例事件流里提取类,而不是从 E-R 图抄表
类图常见误区是从 E-R 图直接映射成数据库表,然后宣称这就是类图。正确做法是从用例的事件流里找名词和动词:名词是候选类,动词是候选方法。比如“病人提交基本信息生成挂号单”,候选类是病人、挂号单、医生;“收费处理”对应挂号单上的计费方法。
下面用 Python dataclass 演示核心类的定义,属性名与数据字典条目对应。
# class_design.py from dataclasses import dataclass, field from datetime import date @dataclass class Patient: patient_id: str # 病历号,对应 E-R 图中的主键 name: str gender: str # 性别 age: int # 年龄,对应数据字典 Shu-004 @dataclass class Doctor: doctor_id: str name: str title: str # 职称,普通/专家/主任判断依据 @dataclass class Registration: ticket_id: str # 挂号单编号,测试环节要求 5 位数字 patient: Patient doctor: Doctor ticket_type: str # 挂号类型,取值普通/专家/主任 fee: float = 0.0 status: str = "created" # 初始状态,与状态图保持一致 def charge(self, price_table: dict) -> None: # 按挂号类型查价目表,更新费用与状态 self.fee = price_table.get(self.ticket_type, 0.0) self.status = "paid"逻辑说明:Patient、Doctor、Registration三个类构成聚合关系——挂号单聚合病人和医生,而不是继承它们。charge方法从price_table字典里查价格,这样新增挂号类型时只需要改价目表,不需要改方法内部,满足开闭原则。status字段从初始值created变更为paid,这个状态迁移必须和 4.4 状态图完全对得上,否则类和状态图就是各画各的。
4.3 顺序图:用时间轴检验类图里的调用次序
顺序图画的是对象之间按时间展开的消息传递。医院的挂号顺序图里至少应有四条生命线:工作人员、挂号单界面、挂号单对象、医生对象。消息顺序大致是:
- 工作人员输入病人信息,界面调用 Patient 构造方法;
- 界面读科室和医生列表,返回匹配的 Doctor 对象;
- 界面创建 Registration 对象并关联 Patient、Doctor;
- 收费模块调用 charge(),根据挂号类型计算费用;
- 系统返回挂号单编号,工作人员打印输出。
画顺序图时最容易犯的错是消息名与类图方法名不一致。类图里写了charge(price_table),顺序图里却画成calculate_fee(),两处名称不同在评审时会被视为设计不同步。建立一条简单的规则:顺序图中的每个消息名,都必须在类图中某个类的方法列表里找得到。如果calculate_fee()在类图中不存在,说明需要补到类图上,而不是反过来改顺序图。
4.4 状态图:挂号单从创建到退号的六个状态迁移
状态图刻画单个对象的生命周期。对挂号单对象来说,常见状态包括:已创建(created)、已支付(paid)、已退号(cancelled)、已就诊(served)。状态迁移如下:
| 当前状态 | 触发事件 | 目标状态 |
|---|---|---|
| created | 收费成功 | paid |
| paid | 病人申请退号,工作人员确认 | cancelled |
| paid | 医生完成接诊 | served |
| cancelled | 无 | 终态 |
| served | 无 | 终态 |
状态图的画法是:每个状态一个圆角矩形,初态用实心圆点,终态用圆点加圆圈。迁移箭头上的标签是触发事件,比如“收费成功”“退号确认”。这里有一个隐藏点:paid 状态到 cancelled 的迁移,在实际业务里还要判断退费规则,比如专家号开诊前 2 小时之外才允许退。如果测试方案或用例图里没有覆盖这个分支,就属于需求不完整,状态图反而把这个缺口暴露出来了。状态图画完之后,把每个迁移与用例事件流对照,补上缺失的异常分支,这是面向对象四张图里最有价值的一次检查。
5. 测试方案:基于边界值的挂号单输入规则推导
5.1 先从三组用例反推业务规则
课设原文的测试方案只有三组输入输出:
- 输入
10021,预期输出“通过”; - 输入
nobody,预期输出“有误”; - 输入
1234j,预期输出“有误”。
从这三组结果反推,被测字段很可能是挂号单编号或病历号,校验规则是“5 位数字,不允许字母和混合串”。10021刚好 5 位且纯数字,通过;nobody是 6 位字母,失败;1234j是数字与字母混合,失败。这个规则用正则表达式描述就是^\d{5}$。
5.2 边界值分析不能只取合法点
如果测试报告只写这三条用例,评审大概率会追问:边界在哪里?对“5 位数字”这个规则,边界值是长度 4、长度 5、长度 6,以及纯数字与非纯数字的切换点。下面这段脚本把边界用例一次性跑完,输出与预期对照。
# boundary_test.py import re def validate_ticket_id(code: str) -> str: # 挂号单编号必须是 5 位数字,这是从测试方案反推的规则 return "通过" if re.fullmatch(r"\d{5}", code) else "有误" cases = [ ("1234", "有误"), # 下邻:4 位 ("12345", "通过"), # 合法:5 位 ("123456", "有误"), # 上邻:6 位 ("nobody", "有误"), # 全字母 ("1234j", "有误"), # 数字加字母 ] for code, expected in cases: actual = validate_ticket_id(code) print(f"输入 {code:<6} 预期 {expected} 实际 {actual} " f"{'PASS' if actual == expected else 'FAIL'}")参数说明:code是被测输入,expected是测试方案定义的预期输出。脚本里用了fullmatch而不是match,因为fullmatch要求整个字符串完全匹配,match只要开头匹配就会放过12345j这类的混合串。三组原始用例只覆盖了场景 2、3、5,补上场景 1、4 后,测试方案的完整度会明显提高,而且这两组是纯手工能想出来的边界。
5.3 测试用例表与回归顺序
课程设计里的测试方案不要求自动化测试,但要求设计文档里有一张能对应需求的测试用例表。这张表可以这样列:
| 用例编号 | 输入 | 预期输出 | 确认的动作状态 |
|---|---|---|---|
| T-001 | 10021 | 通过 | 挂号单进入已创建状态 |
| T-002 | 1234 | 有误 | 提示编号格式错误,不生成挂号单 |
| T-003 | 123456 | 有误 | 同上 |
| T-004 | nobody | 有误 | 同上 |
| T-005 | 1234j | 有误 | 同上 |
回归顺序也有讲究:先跑合法边界 T-001,再跑非法长度 T-002、T-003,最后跑混合类型 T-004、T-005。如果实现代码在验证时能通过前两个却在 T-005 失败,问题多半出在用了isdigit()判断而不是完整正则,因为isdigit()对1234j判断为 False,但正则能明确告诉你错在位置 5。这一层分析写进测试报告,比只贴三条输入输出有说服力得多。
6. 收尾技巧:图表编号、OLE 对象与交稿前的自检方式
6.1 用一张交叉表排查图表不一致
所有图都画完之后,可以建一张“图表元素——源头文档”的交叉表,把每个关键数据项在系统流程图、DFD、数据字典、E-R 图、类图和测试用例表里的出现位置列出来,逐行核对名称:
| 数据项 | 系统流程图 | DFD | 数据字典 | E-R 图 | 类图 | 测试用例 |
|---|---|---|---|---|---|---|
| 病人姓名 | 出现 | 数据流“病人信息” | Shu-001 | 病人实体 | name 属性 | 用例输入 |
| 挂号类型 | 分支条件 | 数据流“挂号信息” | Shu-006 | 挂号单实体 | ticket_type 属性 | 合法/非法取值 |
| 挂号单编号 | 不涉及 | 输出流 | BRD01-02 | 挂号单主键 | ticket_id | 边界值 5 位数字 |
核对方法很简单:从 DFD 里挑一条核心数据流“病人信息”,沿着数据字典找到定义,再看 E-R 图的病人实体有没有对应字段,最后看类图和测试用例是否使用同一名称。只要某一行在任意两列里对不上,就是需要修订的地方。这一步花 15 分钟,能规避答辩时一半的质疑。
6.2 在 Word 里嵌入 Excel 数据字典的正确方式
学校如果要求“数据字典用 Excel 写”,正确的动作是:
- 把数据字典按编号、名称、别名、类型、宽度、取值范围六列整理成 xlsx;
- 在 Word 中定位到数据字典章节,选择“插入 → 对象 → 由文件创建”;
- 浏览选中 xlsx 文件,勾选“显示为图标”则文档里不占版面,双击可展开;
- 取消勾选“链接到文件”,避免文件移动后 Word 里的对象失效。
如果直接把 Excel 表格截图放进去,评阅老师双击无法编辑,会被认定文档编制不规范。还有一个小问题:嵌入前把 Excel 的网格线关掉,打印出来会更干净,“插入——对象——由文件创建——浏览——插入——确定”这条菜单路径在 Word 2016 之后仍适用,旧版 Word 的位置在“插入 → 对象”里的“新建”和“由文件创建”两个页签之间切换。
6.3 交稿前最后过一遍的五处细节
- 每张图下方加“图 X-XX 系统流程图”和图号,正文里第一次出现该图时必须引用同一个图号;
- DFD 第 0 层和第 1 层的数据流名逐一对照;
- 测试用例表里的输入值,必须能在数据字典的取值范围里找到依据;
- 图里的文字注释统一用中文,避免一部分英文一部分中文造成歧义;
- StarUML 生成的图导出为高分辨率图片再插入 Word,避免打印后线条发虚。
图表编号本身应该贯穿始终:系统流程图编号从第 5 章开始排,E-R 图从第 6 章排,面向对象四张图按小节号排;如果报告修改过程中删掉一张图,所有后续编号都要顺延。最后把代码、建模文件和 PDF 版报告放进同一个文件夹,压缩包命名格式建议是“医院在线预约系统_学号_姓名_课设”,这样不管是线上提交还是打印装订,都省去再次沟通的时间。
本文还有配套的精品资源,点击获取