ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

医院管理系统课程设计:从需求分析到Spring Boot落地的完整实践

医院管理系统课程设计:从需求分析到Spring Boot落地的完整实践 简介一份面向软件工程课程设计的医院管理系统完整设计文档围绕医疗业务场景完整演示如何运用软件工程方法完成需求分析与系统设计。文档共1个doc文件压缩包大小279KB章节结构清晰先进行经济与技术可行性分析再结合用例图和字典维护梳理功能需求随后逐一展开门诊挂号、门诊划价收费、门诊医生工作站、住院病人管理、住院费用管理、住院医生工作站、药房管理等核心模块并附业务流程图与系统流程图直观呈现患者就诊、费用结算、药品管理等关键流程。第三章进一步给出系统关系图与数据表设计为数据库建模和后续开发提供可直接参照的模板。整份文档适合软件工程课程设计、毕业设计或项目实训参考能帮助读者理解正规设计文档的结构组织与细节表达。已有103人学习下载对正在完成类似课设的高校学生具有较高借鉴价值。1. 医院管理系统课程设计先定范围再写代码医院管理系统是软件工程课程设计里出现频率最高的一批题目正因为它太常见反而容易做成只有增删改查的 CRUD 演示答辩时被评委一句话问住。这门课程设计的真正考察点不是某个框架用得多熟而是你能否把一个模糊的“医院要管什么”拆解成可验证的需求、可落地的设计、可运行的代码和可回放的过程。我会以“门诊挂号”作为主场景把从需求到数据库再到核心接口的完整路径走一遍并给出文档写作和演示录制的具体技巧。以下内容适合正在做软件工程课程设计或毕业设计选题的在校生也适合需要带新人走完项目全流程的工程师参考。2. 软件工程课程设计的需求分析用用例图与数据字典圈定边界很多小组拿到“医院管理系统”这个题目后的第一反应就是打开 IDE 建表。实际上软件工程课程设计的第一步是需求分析需求分析不是问“系统有哪些菜单”而是问“谁会用它他要完成什么目标”。医院管理系统如果按真实业务做会牵扯到挂号、收费、药房、检验、住院等多个子系统课程设计必须做范围裁剪。我一般会把范围限定为门诊模块去掉药品库存和住院保留完整的“挂号-退号-就诊-统计”闭环这样既能在短期内完成也能把软件工程各个阶段的成果展示完整。2.1 角色与用例划分医院管理系统涉及到的用户可以分为三类挂号员、医生、管理员。课程设计里不需要做患者的自助终端通常由挂号员替患者操作所以实际建模的角色就是这三类患者信息只是被创建和引用的数据对象。角色确定后再往每个角色身上挂用例比如管理员维护科室、维护医生、设置出诊排班、查看挂号统计。挂号员患者建档、挂号、退号、收费。医生查看当日挂号列表、录入诊断结果、结束就诊。UML 用例图的关键是“动词宾语”不要写“用户管理模块”这样的名词。你可以用 StarUML 或 draw.io 画图但更重要的是一张用例图背后每一根连线都要对应一个真实的业务事件。课程设计答辩时老师常问的第一个问题就是“为什么只有这些用例”所以还要补充一句住院、药品、检查化验等业务在真实医院里往往是独立系统这里为了控制复杂度没有纳入。2.2 用 Gherkin 写核心用例的验收条件需求分析不只是画图还需要让用例可验证。我习惯用 Gherkin 语言写核心用例的验收场景因为团队成员和评委都能看懂而且后面能直接转成自动化测试的输入。Gherkin 是 Cucumber 系工具使用的“Given-When-Then”格式不依赖具体编程语言。# 文件: features/register.feature Feature: 门诊挂号 作为挂号员 我想要为到院患者挂当日号 以便患者能按序就诊 Scenario: 正常挂到一个剩余号源 Given 科室心内科存在 And 医生张勇今天有出诊排班 And 该排班剩余号源为10 When 我为患者王芳挂心内科张勇的号 Then 系统生成一条挂号记录 And 排班剩余号源变为9这段 Gherkin 里的 Given 描述前置条件When 描述操作动作Then 描述可观察的结果。实际开发时你可以把它翻译成一次集成测试先插入科室、医生、排班和患者数据再调用挂号接口最后断言挂号记录数和剩余号源数量。这种方式比用自然语言写“系统能够正确挂号”要精确得多后面测试章只需要把这里的场景逐条转成用例表即可。注意“剩余号源”这个字段要出现在数据字典里否则数据库设计会接不上。2.3 数据字典与业务规则数据字典是软件工程课程设计文档里最容易拿分的部分。不要只贴一张表截图要写出字段名、类型、约束、默认值说明。下面是患者表的一个节选用于体现格式字段名类型约束说明patient_idbigintPK, auto_increment患者主键patient_namevarchar(50)not null患者姓名id_cardvarchar(18)unique身份证号退号时校验用phonevarchar(20)nullable联系电话create_timedatetimedefault now()建档时间数据字典之外还必须写业务规则。常见规则包括退号只能退当天的号已经就诊的号不能退一个排班剩余号源不能小于 0同一患者同一时段只能挂一个号。这些规则在后端要用事务和唯一索引同时保证如果只写在文档里而不在代码里实现答辩时很容易被追问。软件工程课程设计里非功能需求同样需要明确。比如高峰期挂号的并发量虽然不会真压测但你要在文档里定义“系统在 50 个并发请求下挂号接口平均响应时间小于 2 秒”。这句话写在需求分析里后面测试章就可以通过并发工具验证形成前后呼应。另一点是安全性身份证号在后端日志里要脱敏挂号员不能访问统计分析接口。这些在需求阶段不定义实现时就会漏掉。3. 医院管理系统数据库设计从 ER 图到建表 SQL 的落地需求分析完成之后进入数据库设计。很多课程设计把数据库设计等同于“建几张表”但实际上需要先从 ER 图推导关系模式再设计主外键、约束和索引。医院管理系统的核心实体是科室、医生、排班、患者、挂号记录围绕这五个实体可以把整个门诊业务串起来。3.1 关系模式与主外键设计从 ER 图到关系模式我一般会走这几步每个实体一张表多对多关系拆中间表。在这个系统里科室与医生是 1:N医生与排班是 1:N排班与挂号记录是 1:N患者与挂号记录是 1:N。挂号记录本身也是一个实体它同时关联患者和排班。下面是初步的关系模式表关系模式主键外键department(dept_id, dept_name, intro)dept_id-doctor(doctor_id, dept_id, name, title)doctor_iddept_idschedule(schedule_id, doctor_id, dept_id, clinic_date, start_time, end_time, total, remain)schedule_iddoctor_id, dept_idpatient(patient_id, name, id_card, phone, create_time)patient_id-registration(reg_id, patient_id, schedule_id, status, create_time)reg_idpatient_id, schedule_id注意 schedule 表里冗余了 dept_id这个字段可以由 doctor 表带出来。按照第三范式应该去掉但我保留了它因为按科室统计挂号量是高频查询冗余一个外键字段能让统计 SQL 少一次 join。课程设计里最常遇到的范式争论就在这种地方要不要为了性能牺牲范式我的答案是可以在设计文档里写清楚“该字段是受控冗余写入时通过代码保证与 doctor.dept_id 一致”。这说明你思考过而不是抄了一个建表脚本。3.2 建表 SQL 脚本下面给出一份能在 MySQL 8.0 运行的建表脚本。重点看 schedule 表上的唯一索引uk_doctor_clinic它的作用是防止同一医生在同一个日期的同一个开始时间被重复录入排班。CREATE TABLE department ( dept_id BIGINT PRIMARY KEY AUTO_INCREMENT, dept_name VARCHAR(50) NOT NULL, intro VARCHAR(200) NULL ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE doctor ( doctor_id BIGINT PRIMARY KEY AUTO_INCREMENT, dept_id BIGINT NOT NULL, doctor_name VARCHAR(50) NOT NULL, title VARCHAR(20) NULL, CONSTRAINT fk_doctor_dept FOREIGN KEY (dept_id) REFERENCES department(dept_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE schedule ( schedule_id BIGINT PRIMARY KEY AUTO_INCREMENT, doctor_id BIGINT NOT NULL, dept_id BIGINT NOT NULL, clinic_date DATE NOT NULL, start_time TIME NOT NULL, end_time TIME NOT NULL, total INT NOT NULL DEFAULT 20, remain INT NOT NULL DEFAULT 20, CONSTRAINT fk_schedule_doctor FOREIGN KEY (doctor_id) REFERENCES doctor(doctor_id), CONSTRAINT fk_schedule_dept FOREIGN KEY (dept_id) REFERENCES department(dept_id), UNIQUE KEY uk_doctor_clinic (doctor_id, clinic_date, start_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE patient ( patient_id BIGINT PRIMARY KEY AUTO_INCREMENT, id_card VARCHAR(18) NOT NULL UNIQUE, patient_name VARCHAR(50) NOT NULL, phone VARCHAR(20) NULL, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE registration ( reg_id BIGINT PRIMARY KEY AUTO_INCREMENT, patient_id BIGINT NOT NULL, schedule_id BIGINT NOT NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT 0-已挂号 1-已就诊 2-已退号, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_reg_patient FOREIGN KEY (patient_id) REFERENCES patient(patient_id), CONSTRAINT fk_reg_schedule FOREIGN KEY (schedule_id) REFERENCES schedule(schedule_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这段 SQL 里schedule 表的 total 是放号总数remain 是剩余号数每次挂号成功都要执行remain remain - 1。为了让扣减操作线程安全SQL 必须写成UPDATE schedule SET remain remain - 1 WHERE schedule_id ? AND remain 0而不是先查再算。字段类型上时间用 DATE 和 TIME 分开不用一个 DATETIME因为医生出诊的日期和时段是分开维护的查询某一天排班时更直接。id_card 用 VARCHAR 而不是 BIGINT原因是身份证号可能带 X而且长度超过 bigint 的上限这类细节在课程设计文档的数据设计章节都值得写上。3.3 关键查询会用到哪些索引很多人建完表就忘了索引。门诊挂号会有两个高频查询按日期查某科室排班、按患者查挂号记录。前者对应idx_schedule_dept_date后者对应idx_reg_patient。下面的索引脚本是必需的CREATE INDEX idx_schedule_dept_date ON schedule(dept_id, clinic_date); CREATE INDEX idx_reg_patient ON registration(patient_id);第一个索引把 dept_id 放在前面因为业务通常先选科室再看日期组合索引能够减少回表次数。第二个索引是 registration 表外键的关联索引MySQL 的外键约束并不会自动创建索引所以需要手动补。这里还有一点值得写进报告为什么不给 registration 表的 status 字段单独加索引因为单独查“已退号”没有业务意义查询通常是“按时间范围统计各状态数量”所以更合理的是创建一个(status, create_time)组合索引。如果你在文档里能解释清楚组合索引的列顺序老师就会认为你理解了索引原理。4. 用 Spring Boot 实现医院管理系统核心挂号流程数据库设计完成后下一步是选择实现技术。课程设计常用的技术栈是 Spring Boot MyBatis-Plus MySQL。Spring Boot 负责依赖管理和自动配置MyBatis-Plus 提供通用 Mapper 和 LambdaQuery能显著减少重复代码。这里我以“挂号”这个核心操作为例讲解三层结构和事务控制。4.1 项目结构与依赖一个清晰的项目结构比代码本身更容易让老师读懂。我通常把代码按 interface 的调用链分层而不是按类类型分。项目结构可以这样组织hospital/ ├── pom.xml ├── src/main/java/com/example/hospital/ │ ├── controller/RegisterController.java │ ├── service/RegisterService.java │ ├── mapper/ScheduleMapper.java │ ├── mapper/RegistrationMapper.java │ ├── entity/Schedule.java │ ├── entity/Registration.java │ └── rule/RegisterRule.java └── src/main/resources/ ├── application.yml └── mapper/pom.xml 里引入spring-boot-starter-web、mybatis-plus-boot-starter、mysql-connector-j三个核心依赖即可。entity 包中放与表对应的实体类mapper 包中放数据库操作接口service 包中放业务逻辑controller 包中只做 HTTP 参数接收和结果封装。application.yml 中需要关心的是数据源和事务配置spring: datasource: url: jdbc:mysql://localhost:3306/hospital?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 transaction: rollback-on-commit-failure: true mybatis-plus: global-config: db-config: id-type: autourl 里的serverTimezoneAsia/Shanghai是 MySQL 8 必带的参数否则连接池启动时会报时区错误。rollback-on-commit-failure的含义是当事务提交阶段发生异常时也执行回滚虽然平时的测试很难触发但加上后能在并发场景下提高一致性。这就是为什么我建议在课程设计里写配置说明而不是只贴配置代码。4.2 挂号接口的 Controller-Service-Mapper 三层实现挂号接口的路径是POST /api/register请求体包含 patientId 和 scheduleId。Controller 层先做基础参数校验然后调用 Service 层。PostMapping(/api/register) public ResultString register(RequestBody RegisterRequest req) { if (req.getPatientId() null || req.getScheduleId() null) { return Result.error(参数不能为空); } return Result.ok(registerService.register(req.getPatientId(), req.getScheduleId())); }Controller 不直接操作数据库也不写业务判断只负责参数存在性校验和响应格式包装。这样做的原因是 Controller 容易写但难以测试把逻辑下沉到 Service 后可以脱离 HTTP 上下文做单元测试。Service 层是核心这里要处理锁、事务和状态。代码如下Transactional public String register(Long patientId, Long scheduleId) { Schedule schedule scheduleMapper.selectByIdForUpdate(scheduleId); if (schedule null) { throw new BizException(排班不存在); } if (schedule.getRemain() 0) { throw new BizException(号源已满); } int updated scheduleMapper.decreaseRemain(scheduleId); if (updated 0) { throw new BizException(号源已满); } Registration reg new Registration(); reg.setPatientId(patientId); reg.setScheduleId(scheduleId); reg.setStatus(0); registrationMapper.insert(reg); return 挂号成功; }这里selectByIdForUpdate使用SELECT ... FOR UPDATE锁定排班行防止两个请求同时读到剩余号 1然后一起扣成 0。decreaseRemain的 SQL 正是前文提到的条件更新UPDATE schedule SET remain remain - 1 WHERE schedule_id #{scheduleId} AND remain 0。这种“先锁后写”的方式在实际项目中很常见。Transactional保证整个方法在一个数据库事务里如果最后插入挂号记录失败前面的扣减会自动回滚。对应的 Mapper 接口也需要把锁查询和扣减更新写清楚public interface ScheduleMapper extends BaseMapperSchedule { Select(SELECT * FROM schedule WHERE schedule_id #{scheduleId} FOR UPDATE) Schedule selectByIdForUpdate(Long scheduleId); Update(UPDATE schedule SET remain remain - 1 WHERE schedule_id #{scheduleId} AND remain 0) int decreaseRemain(Long scheduleId); }注意decreaseRemain的返回值是受影响行数。如果更新行数为 0说明在锁竞争过程中剩余号已经被其他请求扣光这时直接抛出“号源已满”。相比先查询再判断的方式这种写法少了“检查再执行”的竞态窗口。4.3 事务与状态机的三个易错点课程设计答辩时关于事务和状态的问题最容易暴露“是否真正写过代码”。我用一张表总结最常见的三个错误易错点表现正确做法不锁行直接更新高并发下超卖UPDATE ... WHERE remain 0事务方法内部调用Transactional 失效通过代理对象调用或拆成两个类退号不检查状态已就诊的号被退UPDATE ... WHERE reg_id? AND status0事务方法内部调用是 Spring 的一个经典问题。如果在同一个类的另一个方法里直接调用this.register()因为绕过 Spring 代理Transactional不会生效。常见解决办法是把事务方法放到单独的事务类中或者使用AopContext.currentProxy()。退号接口也需要先查状态再改状态不能直接按 reg_id 更新 status2否则已就诊患者能退号就破坏了业务规则。把这三个点写进设计文档你的系统会显得比同组更扎实。提示在实际操作中注册业务如果还涉及短信通知或库存扣减需要引入更复杂的分布式事务方案但在课程设计里把本地事务做好已经足够。5. 课程设计文档验收测试用例与演示录像的写法代码写完还要过验收。软件工程课程设计的评分依据是交付文档和现场演示而不是 GitHub 仓库的 star 数。最后这部分讲测试用例表、演示录像和文档目录怎么写才不扣分。5.1 测试用例表怎么填才不扣分课程设计报告里的测试表最常见的问题是所有行都写“系统运行正常”。正确做法是把需求章里的每个 When 步骤对应到测试用例并且预期结果必须可观测。用例编号测试项前置条件操作步骤预期结果实际结果TC-001正常挂号排班剩余号 10调用 POST /api/register返回成功remain 变为 9通过TC-002号源已满排班剩余号 0调用 POST /api/register返回“号源已满”通过TC-003重复退号挂号记录 status2调用 POST /api/refund返回“非法状态”通过每条用例都要能回到 2.2 节 Gherkin 场景里对应的步骤这样需求、设计、测试三个章节就串成一条线了。5.2 演示录像的脚本顺序演示录像不需要把所有功能都点一遍而是按照一条完整业务主线走登录系统、患者建档、设置排班、挂号、退号、查看统计。每个操作步骤在屏幕上停留 3 秒以上让评委看清路径。如果系统做了权限控制一定要在录像里展示挂号员访问统计接口被拒绝的提示这是很加分的细节。5.3 课程设计报告的目录模板一份完整的软件工程课程设计 doc 通常包括可行性分析、需求分析、总体设计、详细设计、系统实现、测试报告、总结。在每一个章节开头写一段“为什么这样做”比直接贴代码有效。报告里每张图、每个表都要编号并引用一次比如“如图 3-1 所示”这样评委会觉得整个设计是逐步推导出来的而不是从网上拼出来的。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进