尧图精选

Python+Django医院管理系统:毕设从数据建模到部署全流程解析

🕒 发布时间:2026/9/28 4:19:04 📁 来源:尧图网络
简介基于Python的街区医院管理系统毕业设计成套资料包含论文与源码两部分面向计算机科学与软件工程相关专业的毕业生以及需要完成中小型管理信息系统课题的初学者。系统以Django为后端框架、MySQL为数据存储采用B/S架构围绕注册登录、管理员、医生、患者四个角色展开覆盖系统登录、用户管理、医生功能与患者功能等核心模块整体结构清晰、分层明确。压缩包约34.66MB以论文文档和Python源码文件为主论文包含选题背景与研究意义、开发技术选型、需求分析、可行性分析、登录与添加用户等流程图、数据库E/R图与数据表设计、各模块实现说明、系统测试与运行维护等完整章节源码按管理员、医生、患者功能模块组织可与论文逐节对照方便理解系统从设计到落地的全过程。目前已有56人学习适合用于毕业设计整体参考、代码复现或二次开发训练。1. 基于Python的街区医院管理系统一套能直接拿去答辩的毕业设计长什么样看到“基于Python的街区医院管理系统的设计与实现(论文源码)-kaic.zip”这个标题我第一反应就是这就是一个典型的计算机专业毕业设计包。它准确告诉你三件事技术栈是Python业务场景是街区医院也就是社区卫生服务中心级别的小型医院交付物是论文加源码。对于正在选毕设题的人来说最关心的不是“这系统能做得多大”而是“我能不能在一个学期里跑通它、答辩时能不能说清楚”。我的答案是能。它本质上是一个Web信息管理系统覆盖挂号、门诊、收费、药房这几条核心业务流程用Django或Flask这类框架都做得明白。这篇笔记我会按实际开发顺序从需求建模、环境搭建、模块串联一直讲到避坑和验收打包全程给出可复现的代码和参数你照着做就能得到一个能演示、能写进论文的系统骨架。2. 把需求拆成数据表街区医院管理系统的模块边界与ER设计2.1 街区医院与三甲HIS的差别为什么不用微服务很多第一次做毕设的人一上来就想着用Spring Cloud、微服务、消息队列把系统设计成“企业级”。但街区医院管理系统首先服务的是单院区、单日几百号量的场景核心诉求是“挂号别排长队、处方别丢、收费对得上账”。它不是三甲医院那种需要异构系统对接、高并发、多租户的HISHospital Information System。所以我一般会建议选Python后端框架时优先用Django而不是FlaskDjango自带Admin后台、ORM、迁移工具和表单校验能省下大量重复代码而这些时间正好用来打磨业务细节和论文。架构上就拆单应用、一个数据库模块上用Django的app做物理隔离比微服务更适合单人开发、单人答辩的项目。2.2 核心实体患者、医生、挂号单、处方、收费记录的数据模型不管是写论文里的ER图还是直接建表第一步都是定实体。街区医院管理系统最少要覆盖以下实体科室Department、医生Doctor、患者Patient、挂号单Registration、处方Prescription、收费记录ChargeRecord。它们之间的关系是一个科室有多名医生一个患者可以多次挂号一次挂号对应一次就诊医生在一次就诊中可开处方处方上的药品项目最终汇总成收费记录。这里有一个大多数人容易忽略的点挂号单是整个系统的“业务主键”。所有后续动作——就诊、开药、收费、发药——都应该通过挂号单ID关联而不是直接用患者ID去查。原因很简单患者上午挂A医生看感冒下午挂B医生看牙如果用患者ID关联数据会串。这也是答辩时评委最常追问的“数据冗余和关联一致性”问题。2.3 用Django ORM建模的代码与字段参数在Django里数据表就是models.py中的类。下面是一个可直接运行的模型设计覆盖上述核心实体from django.db import models class Department(models.Model): name models.CharField(科室名称, max_length50, uniqueTrue) floor models.IntegerField(所在楼层, default1) def __str__(self): return self.name class Doctor(models.Model): name models.CharField(医生姓名, max_length30) title models.CharField(职称, max_length20, blankTrue) department models.ForeignKey(Department, on_deletemodels.CASCADE, verbose_name所属科室) phone models.CharField(联系电话, max_length20, blankTrue) class Meta: ordering [department_id, id] def __str__(self): return f{self.name}{self.department.name} class Patient(models.Model): GENDER_CHOICES [(M, 男), (F, 女)] name models.CharField(患者姓名, max_length30) gender models.CharField(性别, max_length2, choicesGENDER_CHOICES, defaultM) birth_date models.DateField(出生日期, nullTrue, blankTrue) phone models.CharField(手机号, max_length20) address models.CharField(住址, max_length200, blankTrue) def __str__(self): return f{self.name}{self.phone} class Registration(models.Model): STATUS_CHOICES [ (PENDING, 待诊), (FINISHED, 已完成), (CANCELLED, 已取消), ] patient models.ForeignKey(Patient, on_deletemodels.CASCADE, verbose_name患者) doctor models.ForeignKey(Doctor, on_deletemodels.CASCADE, verbose_name医生) registered_time models.DateTimeField(挂号时间, auto_now_addTrue) fee models.DecimalField(挂号费, max_digits8, decimal_places2, default0) status models.CharField(状态, max_length10, choicesSTATUS_CHOICES, defaultPENDING) def __str__(self): return f挂号单#{self.id} {self.patient.name} {self.doctor.name} class Drug(models.Model): name models.CharField(药品名, max_length50) price models.DecimalField(单价, max_digits8, decimal_places2) stock models.IntegerField(库存, default100) def __str__(self): return self.name class Prescription(models.Model): registration models.ForeignKey(Registration, on_deletemodels.CASCADE, verbose_name挂号单) drug models.ForeignKey(Drug, on_deletemodels.PROTECT, verbose_name药品) quantity models.PositiveIntegerField(数量, default1) created_time models.DateTimeField(开单时间, auto_now_addTrue) def __str__(self): return f处方{self.id}: {self.registration.patient.name} - {self.drug.name} x{self.quantity} class ChargeRecord(models.Model): registration models.ForeignKey(Registration, on_deletemodels.CASCADE, verbose_name挂号单) total_amount models.DecimalField(应收金额, max_digits10, decimal_places2) paid_time models.DateTimeField(收费时间, auto_now_addTrue) is_paid models.BooleanField(已支付, defaultFalse) def __str__(self): return f收费单{self.id}: 金额{self.total_amount} 状态{self.is_paid}这段模型里几个参数值得解释。max_length不仅是长度限制在MySQL里它直接影响VARCHAR列的实际最大长度比如你写max_length200生成的就是varchar(200)超过会报错或截断。choices字段约束虽然可以在Python层做校验但Django的迁移并不会在数据库层生成CHECK约束所以如果有多人并发写入最好再在表单层做二次校验。on_deletemodels.CASCADE代表外键关联的数据删除时子数据一起删比如删掉一个挂号单它下面的处方和收费记录也会消失这在毕业设计里是符合“整单退掉”的业务逻辑的。而处方里对Drug用on_deletemodels.PROTECT是为了防止删除一个还有历史处方引用的药品否则会造成“处方里的药名变成了空指针”。这些都是答辩时会问到的东西。3. 用DjangoMySQL跑通最小系统从空项目到第一个挂号页面3.1 环境准备Python版本、虚拟环境、依赖安装开始写代码前先把环境收拾明白。我做毕设带学生时见过太多人在python安装教程里只装了Python解释器不建虚拟环境结果系统不同项目依赖全搅在一起。这里给一套固定的步骤适用于Windows和Linux/macOS。Python版本建议3.8到3.11Django选择成熟的4.2 LTS版本MySQL用8.0以上也可以但要留意后面说的坑。# 创建虚拟环境名字随意比如venv python -m venv venv # 激活Windows venv\Scripts\activate # 激活Linux/macOS source venv/bin/activate # 升级pip并安装依赖 python -m pip install --upgrade pip pip install django4.2.* mysqlclient这里的mysqlclient是让Django连接MySQL的驱动。很多人按照python安装教程装完卡在mysqlclient编不过原因是系统里缺libmysqlclient-devLinux或没有安装MySQL C库。如果你在Windows上安装失败最省事的做法是下载一个预编译的mysqlclient轮子文件然后用pip install 下载的.whl安装不要在源码编译上死磕。当然如果你只是想快速跑通逻辑也可以先改用SQLite最后再切到MySQL第5章我会专门说这个迁移坑。3.2 创建项目和应用Django项目结构与app划分在激活的虚拟环境里用Django命令行生成项目骨架# 创建项目项目名用hmsHospital Management System django-admin startproject hms # 进入项目目录 cd hms # 创建两个核心应用registration挂号与收费、pharmacy药房 python manage.py startapp registration python manage.py startapp pharmacy为什么不把所有功能写在一个app里因为Django的app天然对应业务模块一个app管“看病开单”另一个管“药品库存”之后在settings.py里注册时清晰在写论文画模块图时也能直接引用这个结构。如果你后面想加统计报表可以再开一个dashboardapp互不干扰。3.3 配置MySQL连接与迁移settings.py中DATABASES参数打开hms/settings.py修改数据库配置。如果你现在先用SQLite就把ENGINE换成django.db.backends.sqlite3NAME写数据库文件的绝对路径。下面直接给MySQL版本DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: hms_db, USER: root, PASSWORD: 你的数据库密码, HOST: 127.0.0.1, PORT: 3306, OPTIONS: { charset: utf8mb4, }, } }参数说明ENGINE指定数据库驱动NAME是已经在MySQL里建好的数据库名注意不是表名而是库名。HOST和PORT按你的MySQL服务实际地址填。关键在OPTIONS里的charset: utf8mb4这能避免中文乱码也支持存储emoji之类的四字节字符。utf8mb4是MySQL真正的中文完整编码老项目的utf8其实是utf8mb3遇到生僻字会变问号。改完配置后执行迁移命令把Django内置表和我们的模型同步到数据库python manage.py makemigrations python manage.py migratemakemigrations会检测models.py的变化生成迁移文件migrate才真正执行建表。如果migrate时报错“Unknown database hms_db”说明MySQL里还没这个库先到MySQL命令行执行CREATE DATABASE hms_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;。这一步是最常见的“明明配置没错却连不上”的原因。3.4 写一个挂号接口视图、URL和模板环境通了现在让用户能通过浏览器提交一个挂号单。先创建一个表单Django的ModelForm能自动从模型生成表单字段# registration/forms.py from django import forms from .models import Registration, Patient, Doctor class RegistrationForm(forms.Form): patient_name forms.CharField(max_length30, label患者姓名) patient_phone forms.CharField(max_length20, label手机号) patient_gender forms.ChoiceField(choices[(M,男),(F,女)], label性别) doctor forms.ModelChoiceField(querysetDoctor.objects.all(), label选择医生)# registration/views.py from django.shortcuts import render, redirect from .models import Registration, Patient from .forms import RegistrationForm def create_registration(request): if request.method POST: form RegistrationForm(request.POST) if form.is_valid(): # 先尝试找到患者找不到就新建 patient, _ Patient.objects.get_or_create( phoneform.cleaned_data[patient_phone], defaults{ name: form.cleaned_data[patient_name], gender: form.cleaned_data[patient_gender], } ) # 创建挂号单状态默认待诊 Registration.objects.create( patientpatient, doctorform.cleaned_data[doctor], fee10, # 固定挂号费也可以从Doctor表取 ) return redirect(registration_success) else: form RegistrationForm() return render(request, registration/create.html, {form: form})# hms/urls.py 配置路由 from django.urls import path from registration import views urlpatterns [ path(register/, views.create_registration, namecreate_registration), path(success/, lambda request: render(request, registration/success.html), nameregistration_success), ]逻辑说明视图函数里先实例化表单is_valid()会校验字段长度、必填项等。校验通过后Patient.objects.get_or_create是个很实用的API它先按phone查患者查不到就按defaults里的字段新建返回的患者对象第一个参数是实例第二个是是否新建的标志。这里我用_忽略掉了标志因为业务上不需要区分“新老患者”。最后Registration.objects.create直接插一条挂号记录。其中redirect返回一个HTTP 302响应防止用户刷新浏览器时重复提交表单——这个细节在答辩时提一句“我做了一表单防重提交”能给评委留下好印象。模板文件registration/templates/registration/create.html自己写一个简单的HTML表单即可里面用{{ form.as_p }}把表单渲染成段落注意form的CSRF token要加{% csrf_token %}。CSRF是Django的安全防护不加会报403错误这是新手几乎必踩的坑。4. 核心业务闭环挂号、收费、药房三个模块怎么串起来4.1 业务状态机挂号单的状态流转一个能被实际使用的医院系统挂号单不是“创建”就完事了。从患者进院到离院挂号单状态要经历待诊 - 已完成中间还可能已取消。如果开了药但没交费那收费记录就是未支付状态。这些状态我在模型里已经用STATUS_CHOICES定义好了。状态机设计的原则是状态只能由后端代码主动变更而不是靠前端页面刷新时顺便改。比如医生看完诊要在视图里写一个“完成诊疗”的接口把挂号单状态从PENDING改成FINISHED同时自动汇总处方金额生成收费记录。如果把状态变更逻辑散落在模板里的if语句中后期调试会非常痛苦。4.2 收费模块与药房发药的联动事务处理现在说一个实际开发中最容易出错、也是论文里最有“技术含量”的部分当收费完成时药房库存必须同步扣减。这两步操作不能“先扣库存但收费失败”也不能“先收费但库存扣减失败”。在MySQL和Django里最简单的保证办法就是用事务# pharmacy/views.py from django.db import transaction from django.shortcuts import get_object_or_404 from .models import ChargeRecord, Drug, Prescription from registration.models import Registration def pay_registration(request, registration_id): reg get_object_or_404(Registration, idregistration_id) if reg.status ! PENDING: return HttpResponseBadRequest(挂号单状态不允许收费) # 累计该挂号单项下所有处方金额 prescriptions Prescription.objects.filter(registrationreg) total sum(p.drug.price * p.quantity for p in prescriptions) # 在整个事务里完成创建收费记录、扣库存、改状态 with transaction.atomic(): charge ChargeRecord.objects.create( registrationreg, total_amounttotal, is_paidTrue, ) for p in prescriptions: drug Drug.objects.select_for_update().get(idp.drug_id) if drug.stock p.quantity: raise ValueError(f{drug.name} 库存不足) drug.stock - p.quantity drug.save() reg.status FINISHED reg.save() return JsonResponse({charge_id: charge.id, total: str(charge.total_amount)})这段代码的关键是transaction.atomic()。它保证要么try块里所有数据库操作都成功要么全部回滚。select_for_update()是行级锁在并发情况下防止两个窗口同时扣同一药品库存导致超卖。这里的total计算用了Python层求和数据量只有几百条时性能没问题这也是毕设级别系统最直白的写法。答辩时评委如果问“并发扣库存怎么办”你能说出select_for_update基本就是加分项。需要注意sum(p.drug.price * p.quantity for p in prescriptions)里用Decimal乘法返回也是Decimal不会出现浮点数精度漂移。如果数据库里药品价格字段你用FloatField那这里就可能出现38.59变成38.589999的尴尬所以模型里一律用DecimalField。4.3 用Django admin作为后台管理的捷径注册模型与自定义显示很多人做毕设一方面要写“管理端”一方面又没时间写全套后台页面。Django自带的Admin后台就是给这个场景用的“后悔药”。在admin.py里注册模型就能获得一个可登录的Web管理界面能增删改查所有表还能过滤、搜索。# registration/admin.py from django.contrib import admin from .models import Department, Doctor, Patient, Registration admin.register(Department) class DepartmentAdmin(admin.ModelAdmin): list_display (id, name, floor) admin.register(Doctor) class DoctorAdmin(admin.ModelAdmin): list_display (id, name, title, department) search_fields [name, department__name] admin.register(Patient) class PatientAdmin(admin.ModelAdmin): list_display (id, name, gender, phone, birth_date) search_fields [name, phone] admin.register(Registration) class RegistrationAdmin(admin.ModelAdmin): list_display (id, patient, doctor, fee, status, registered_time) list_filter (status, doctor__department) date_hierarchy registered_time参数说明list_display控制后台列表显示哪些列search_fields里的department__name是跨表搜索双下划线是Django ORM查询外键字段的固定语法list_filter会在右侧生成筛选器date_hierarchy在列表顶部生成按日期快速导航的控件。这些配置不仅可以用于真管理还能直接点击列表里的编辑按钮模拟业务操作比如手动把挂号单状态从待诊改成完成在写自动化前端之前非常好用。注意使用Admin前需要先创建管理员账号python manage.py createsuperuser然后用这个账号访问/admin路径。5. 避坑毕业设计里最让人翻车的5个坑以及解法5.1 现象django.db.utils.OperationalError: (1054, Unknown column ...)原因数据库表里没有这个列。常见于你改了models.py但只执行了makemigrations忘记执行migrate或者换了一台电脑直接把旧的数据库文件拷到新项目模型和表结构不一致。另外如果你用的是MySQL 8.0以上的默认认证插件caching_sha2_password而mysqlclient版本太低也会报一些诡异的连接错误。解决每次修改模型后依次执行python manage.py makemigrations和python manage.py migrate。如果已经不一致可以在开发阶段直接删除应用下的migrations目录里的迁移记录并删掉数据库重建但千万别在“答辩前一周”才这么干数据会全丢。最稳妥的是在项目一开始就把迁移文件纳入版本管理每次提交代码时一起提交迁移文件。5.2 现象写入数据后中文全部变成问号原因数据库的默认字符集不是utf8mb4。你可以用SHOW CREATE DATABASE hms_db;查看如果看到utf8不带mb4甚至latin1那中文就会乱码。这个坑在win7时代的教育机构里特别多因为MySQL安装向导有时候默认选latin1。解决创建数据库时显式指定字符集CREATE DATABASE hms_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;。同时检查settings.py里OPTIONS的charset换成utf8mb4。如果已经乱码了数据无法恢复只能重建库并重新录入。所以在做数据处理前一定先把字符集一次性配好。5.3 现象静态文件CSS、JS在浏览器里加载不出来样式全丢原因开发环境里Django默认不自动提供静态文件服务除非你在urls.py里配置了static()或者设置了STATICFILES_DIRS并且DEBUGTrue。我见过很多人带着“python入门教程里教的模板”直接套但模板里引用的静态资源路径跟Django配置对不上。解决在settings.py里设置STATIC_URL /static/并在项目根创建static目录然后在urls.py最后加一行from django.conf.urls.static import staticurlpatterns static(settings.STATIC_URL, document_rootsettings.STATIC_ROOT)。注意这一步只适用于DEBUGTrue的开发环境。部署到生产环境时要用Nginx等服务器来服务静态文件这是另一个话题但答辩阶段只要你把开发环境跑通就够了。5.4 现象运行migrate时报“Access denied for user root”原因MySQL里root账号默认可能只允许从localhost连接而你的HOST写的是127.0.0.1虽然是本机但MySQL会把localhost和127.0.0.1看成两个不同的host。另外如果没有设置密码或密码包含特殊字符也可能导致连接失败。解决先检查MySQL的账号权限SELECT user, host FROM mysql.user;确认root的host是localhost还是%。如果是localhost那你把settings.py里HOST改成localhost。如果希望用127.0.0.1连接就执行MySQL语句CREATE USER root127.0.0.1 IDENTIFIED BY 密码;并授权。这是很多python新手容易忽略的环境细节。5.5 现象表单提交后刷新数据重复插入原因视图里Registration.objects.create在每次POST请求时都执行一次如果用户提交成功后按F5刷新浏览器会重新提交同一个POST请求导致生成两条挂号单。这个问题本科毕业设计里几乎天天见到。解决就是我在3.4节说的创建成功后用redirect做一次302重定向而不是直接render一个成功页面。重定向会让浏览器地址变成/success/刷新时只是重复GET不会再次执行create。如果你想做得更严谨还可以在数据库里对挂号单的“患者医生当日”加唯一约束但作为毕设重定向已经足够让答辩评委点头了。6. 把论文和源码打包前验证系统完整性的一串命令与自查清单6.1 用Django自带测试跑通最小冒烟测试离交付前一周别急着打zip包先跑一遍测试。Django的测试框架能对着模型和视图做微型验证。写一个最简单的tests.py# registration/tests.py from django.test import TestCase from django.urls import reverse from .models import Patient, Doctor, Department class RegistrationFlowTest(TestCase): def test_create_patient_and_register(self): dept Department.objects.create(name内科, floor1) doctor Doctor.objects.create(name张医生, departmentdept) response self.client.post(reverse(create_registration), { patient_name: 测试患者, patient_phone: 13800000000, patient_gender: M, doctor: doctor.id, }) self.assertEqual(response.status_code, 302) # 重定向到成功页 self.assertEqual(Patient.objects.count(), 1) self.assertEqual(Registration.objects.count(), 1)运行python manage.py test如果通过说明最基本的挂号流程是通的。测试用例里的self.client.post模拟了浏览器POST请求reverse自动解析URL名称比硬编码路径更抗健。这份测试代码本身也可以写进论文的“系统测试”章节一举两得。6.2 生成ER图与数据字典给论文用的图表从哪来论文里必须有ER图和数据库表结构说明。不要自己在画图软件里手动画那既费时又容易和代码不一致。常见做法是用Django的django-extensions库它能从模型自动生成Graphviz格式的ER图pip install django-extensions pydotplus然后在settings.py的INSTALLED_APPS里加django_extensions再执行python manage.py graph_models -a -o er.png生成的er.png里就包含了所有外键关系。如果你不想装Graphviz也可以在MySQL里通过mysqldump导出建表语句然后整理成“数据字典”表格每张表列出字段名、类型、含义、约束。这个表放在论文附录里非常实用。6.3 打包交付技巧requirements.txt、README和数据库初始化脚本“论文源码”的zip里除了论文文档源码包里至少要有这几样东西requirements.txt、README.md、db.sqlite3如果你用的是SQLite或init.sql。requirements.txt用来锁定依赖版本别人拿到你的项目后执行pip install -r requirements.txt就能复现环境。生成方法很简单pip freeze requirements.txt但pip freeze会把你调试时装的一堆无关库都列进去不太干净。更好的是手动写一个最小清单就像这样Django4.2.10 mysqlclient2.2.0README.md里面写清楚三块内容Python版本、虚拟环境的创建命令、从代码到跑起来的步骤迁移、创建管理员、启动服务。不要觉得自己写得啰嗦答辩老师经常现场拷源码查看这份README就是你的“使用说明书”。最后提醒一个我踩过三次的教训zip包在提交前一定要解压到一个全新目录按README从头到尾来过一遍。很多人就是在自己电脑上能跑换到老师电脑上因为缺少一个py后缀安装步骤直接翻车。我现在的习惯是封包前先删掉venv、__pycache__这些临时目录再在命令行里严格按README操作一遍。这些细节看着琐碎却决定你这套毕业设计是“能直接运行”还是“仅供观赏”。希望这份方案能帮到你少走点弯路。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →