
简介这是一套面向高校计算机相关专业学生的医院挂号诊疗管理系统完整项目源码采用PythonDjangoMySql技术栈开发适合用作毕业设计、期末大作业或课程设计参考。项目代码附带详细注释新手也能快速理解整体架构与业务逻辑下载后简单部署即可运行使用。资源包共包含2000个文件以1633个JavaScript脚本、249个HTML页面、53个CSS样式表及33个HTM页面为主体另有JSON配置、Python后端文件、XML与Markdown说明等压缩包大小约6.25MB前端页面与后端逻辑分层清晰。目前已有331人学习下载属于经过实际答辩检验的高分项目导师认可度较高。读者可获得完整的挂号、诊疗、用户管理等模块实现方案借助注释快速掌握Django项目结构与数据库设计思路并在此基础上进行二次开发或功能扩展为毕业设计提供可靠参考。1. 从一份能跑通的挂号系统源码说起它到底解决了什么问题很多计算机专业的同学在毕设选题阶段会卡在同一个地方想做业务系统又怕功能太单薄被答辩老师追问想堆技术栈又担心自己搭不起来环境。这份基于 Python Django MySQL 的医院挂号诊疗管理系统恰好落在了一个比较舒服的区间——业务闭环完整技术栈主流代码结构清晰数据库脚本齐全。它覆盖了患者注册登录、科室与医生管理、排班放号、在线挂号、就诊记录、处方与缴费这条主线后台还有管理员对医生、科室、药品的维护入口。换句话说这不是一个只有增删改查的玩具项目而是一个能讲清楚「谁在什么场景下做了什么操作」的完整业务系统。适合正在做毕设的本科生、需要快速搭一个医疗类管理系统原型的开发者以及想拿一个真实 Django 项目练手的人。下面我会按「先跑起来、再看结构、最后避坑」的顺序把这份资源拆开讲透。2. 环境搭建与项目启动从 Python 到 MySQL 的完整链路2.1 技术栈选型为什么是 Django 而不是 Flask拿到一份源码第一件事不是急着pip install而是先判断它的技术选型是否合理这决定了你后面改代码时会不会被框架本身绊住。这份项目用的是 Django而不是 Flask 或 FastAPI原因很实际医院挂号系统天然包含大量后台管理需求——医生信息维护、科室分类、药品库存、排班表这些用 Django 自带的 admin 后台几乎可以零成本搭出来。如果用 Flask你得自己写一套 CRUD 页面和权限控制工作量翻倍不说还容易在权限校验上出漏洞。Django 的 ORM 也是关键。挂号系统里「一个患者对应多条挂号记录一条挂号记录对应一个医生和一个排班」这种多表关联非常密集用 Django 的ForeignKey和related_name能直接把关系映射到代码层查询时用select_related就能减少数据库往返。MySQL 作为关系型数据库配合 Django 的 migration 机制建表和改字段都不需要手写 SQL。这套组合在毕设场景下属于「稳」的选择答辩时也能讲清楚为什么不用 NoSQL——医疗数据强一致性要求高关系模型更合适。2.2 环境准备Python、MySQL 与依赖安装在动手之前先把三个东西装好Python 3.8 以上、MySQL 5.7 或 8.0、以及一个趁手的编辑器VS Code 或 PyCharm 都行。Python 安装时记得勾选「Add to PATH」否则后面命令行里python会提示找不到命令。MySQL 安装过程中会让你设置 root 密码这个密码后面要写进 Django 的配置文件别设得太随意。# 查看 Python 版本确认在 3.8 以上 python --version # 查看 MySQL 是否安装成功并启动 mysql --version # Windows 下检查服务状态 net start | findstr MySQL # macOS/Linux 下检查 systemctl status mysql确认环境没问题后进入项目根目录安装依赖。这类毕设项目通常会带一个requirements.txt里面列了 Django、mysqlclient、Pillow 等包。mysqlclient是 Python 连接 MySQL 的驱动安装时在 Windows 上偶尔会报编译错误遇到的话换成pymysql也能用但需要在__init__.py里加一行pymysql.install_as_MySQLdb()。# 创建虚拟环境避免污染全局包 python -m venv venv # 激活虚拟环境 # Windows: venv\Scripts\activate # macOS/Linux: source venv/bin/activate # 安装项目依赖 pip install -r requirements.txt # 如果 mysqlclient 安装失败改用 pymysql pip install pymysql虚拟环境这一步很多人会跳过觉得麻烦。但毕设项目往往依赖版本比较旧比如 Django 3.2 和 Django 4.0 在某些 API 上不兼容如果你全局环境里已经装了新版本直接跑就会报一堆ImportError。用虚拟环境隔离是省后悔药的做法。2.3 数据库配置与初始化settings.py 里改什么Django 的数据库配置集中在settings.py的DATABASES字段。这份项目用的是 MySQL你需要把NAME、USER、PASSWORD、HOST、PORT改成自己本地的实际值。NAME是数据库名建议提前在 MySQL 里建好字符集用utf8mb4否则存中文姓名或地址时会出现乱码。# settings.py 数据库配置片段 DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: hospital_registration, # 数据库名需提前创建 USER: root, # MySQL 用户名 PASSWORD: your_password, # 改成你自己的密码 HOST: 127.0.0.1, # 本地用 127.0.0.1不要用 localhost PORT: 3306, # MySQL 默认端口 OPTIONS: { charset: utf8mb4, }, } }这里有个细节值得说HOST写127.0.0.1而不是localhost。在部分系统上localhost会走 Unix socket 连接而 MySQL 的 socket 路径配置不一致时会报Error 2002 (HY000): Cant connect to local MySQL server through socket。用127.0.0.1强制走 TCP能绕开这个玄学问题。数据库建好后执行迁移和初始化# 创建数据库在 MySQL 命令行里执行 CREATE DATABASE hospital_registration CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; # 生成迁移文件并建表 python manage.py makemigrations python manage.py migrate # 导入项目自带的初始数据如果有 .sql 文件 mysql -u root -p hospital_registration init_data.sql # 创建后台管理员账号 python manage.py createsuperuser # 启动开发服务器 python manage.py runservermakemigrations是根据模型文件生成建表语句migrate才是真正在数据库里执行。如果项目自带init_data.sql里面通常包含科室、医生、药品的基础数据导入后前台才有内容可展示。createsuperuser创建的账号用来登录/admin后台管理医生和排班。2.4 项目目录结构与核心模块定位启动成功后别急着点页面先花十分钟把目录结构摸清楚。Django 项目的组织方式比较固定但每个毕设的 app 划分不一样。常见做法是按业务拆成user患者、doctor医生、registration挂号、admin_app后台管理几个 app。每个 app 下有models.py数据模型、views.py业务逻辑、urls.py路由、templates页面模板。目录/文件作用改动频率manage.py命令行入口跑服务、迁移都靠它不改settings.py全局配置数据库、静态文件、中间件改数据库和静态路径urls.py项目级总路由分发到各 app加新页面时改models.py定义表结构和关联关系改字段时改views.py处理请求、查数据、返回页面改业务逻辑时改templates/HTML 模板Django 模板语法改页面时改static/CSS、JS、图片基本不改media/用户上传的文件如医生头像自动生成搞清楚哪个功能在哪个 app 里后面改需求时才能快速定位。比如要加一个「患者取消挂号」功能你需要在registration/views.py里加视图函数在registration/urls.py里加路由在模板里加按钮。这三步缺一不可漏了路由就会 404。3. 核心业务逻辑拆解挂号、排班与权限控制怎么实现3.1 数据模型设计患者、医生、排班、挂号四张表的关联挂号系统的核心在于表关系设计。这份项目里最关键的几张表是患者表、医生表、排班表、挂号记录表。它们之间的关系可以用一句话概括一个医生有多个排班一个排班对应多个挂号名额一个患者可以有多条挂号记录。# models.py 核心模型示例简化版 from django.db import models from django.contrib.auth.models import User class Department(models.Model): name models.CharField(max_length50, verbose_name科室名称) description models.TextField(blankTrue, verbose_name科室描述) def __str__(self): return self.name class Doctor(models.Model): user models.OneToOneField(User, on_deletemodels.CASCADE) department models.ForeignKey(Department, on_deletemodels.CASCADE, verbose_name所属科室) title models.CharField(max_length20, verbose_name职称) specialty models.CharField(max_length100, verbose_name擅长领域) photo models.ImageField(upload_todoctor_photos/, blankTrue) def __str__(self): return f{self.user.last_name}{self.title} class Schedule(models.Model): doctor models.ForeignKey(Doctor, on_deletemodels.CASCADE, verbose_name医生) date models.DateField(verbose_name出诊日期) time_slot models.CharField(max_length20, choices[ (morning, 上午), (afternoon, 下午), ], verbose_name时段) total_slots models.IntegerField(default20, verbose_name总号源) remaining_slots models.IntegerField(default20, verbose_name剩余号源) class Meta: unique_together (doctor, date, time_slot) # 防止重复排班 class Registration(models.Model): patient models.ForeignKey(User, on_deletemodels.CASCADE, verbose_name患者) schedule models.ForeignKey(Schedule, on_deletemodels.CASCADE, verbose_name排班) status models.CharField(max_length10, choices[ (pending, 待就诊), (completed, 已完成), (cancelled, 已取消), ], defaultpending, verbose_name状态) created_at models.DateTimeField(auto_now_addTrue, verbose_name挂号时间) class Meta: unique_together (patient, schedule) # 同一患者同一排班不能重复挂号这段代码里有几个设计点值得注意。Schedule表里的unique_together约束防止同一个医生在同一天同一时段被重复排班这是业务上的硬性要求。Registration表里的unique_together防止患者重复挂号但实际业务中患者可能取消后重新挂号所以更严谨的做法是用status字段配合查询逻辑来判断而不是单纯靠数据库约束。remaining_slots字段是冗余设计每次挂号成功后减一这样查询余号时不用实时统计挂号记录数性能更好但代价是需要在取消挂号时加回去逻辑要写对。3.2 挂号流程的视图函数从选科室到确认挂号挂号流程通常分三步选科室 → 选医生和排班 → 确认挂号。对应的视图函数要处理 GET 请求展示页面和 POST 请求提交数据。下面是一个典型的挂号视图# views.py 挂号核心逻辑 from django.shortcuts import render, redirect, get_object_or_404 from django.contrib.auth.decorators import login_required from django.db import transaction from .models import Department, Doctor, Schedule, Registration login_required def department_list(request): 展示所有科室 departments Department.objects.all() return render(request, registration/department_list.html, {departments: departments}) login_required def doctor_list(request, department_id): 展示某科室下的医生及其排班 department get_object_or_404(Department, iddepartment_id) doctors Doctor.objects.filter(departmentdepartment).select_related(user) # 只展示今天及以后的排班 schedules Schedule.objects.filter( doctor__indoctors, date__gtetimezone.now().date(), remaining_slots__gt0 ).select_related(doctor__user) return render(request, registration/doctor_list.html, { department: department, doctors: doctors, schedules: schedules, }) login_required transaction.atomic def register(request, schedule_id): 确认挂号使用事务保证号源扣减和挂号记录同时成功 schedule get_object_or_404(Schedule, idschedule_id) # 检查是否重复挂号 if Registration.objects.filter(patientrequest.user, scheduleschedule).exists(): return render(request, registration/error.html, {msg: 您已挂过该号请勿重复操作}) # 检查余号 if schedule.remaining_slots 0: return render(request, registration/error.html, {msg: 该时段号源已满}) # 扣减号源并创建挂号记录 schedule.remaining_slots - 1 schedule.save() Registration.objects.create(patientrequest.user, scheduleschedule) return redirect(registration:my_registrations)transaction.atomic装饰器是这段代码的关键。挂号涉及两个写操作扣减remaining_slots和创建Registration记录。如果扣减成功但创建记录失败号源就白白少了一个患者却没挂上号。用事务包起来两个操作要么都成功要么都回滚。select_related用于减少查询次数Doctor.objects.filter(...).select_related(user)会在一次查询里把关联的 User 数据也取出来避免在模板里循环访问doctor.user.last_name时反复查库。3.3 权限控制患者、医生、管理员三种角色的隔离系统里有三类人患者、医生、管理员。Django 自带的User模型和is_staff、is_superuser字段可以区分管理员但医生和患者需要额外标记。常见做法是给User加一个role字段或者用Doctor模型的存在与否来判断——如果某个 User 关联了 Doctor 记录他就是医生。# 用装饰器限制角色访问 from django.contrib.auth.decorators import user_passes_test def is_doctor(user): return hasattr(user, doctor) def is_patient(user): return not hasattr(user, doctor) and not user.is_staff user_passes_test(is_doctor) def doctor_schedule_manage(request): 医生管理自己的排班 schedules Schedule.objects.filter(doctorrequest.user.doctor) return render(request, doctor/schedule_manage.html, {schedules: schedules}) user_passes_test(is_patient) def my_registrations(request): 患者查看自己的挂号记录 registrations Registration.objects.filter( patientrequest.user ).select_related(schedule__doctor__user, schedule__doctor__department) return render(request, registration/my_registrations.html, {registrations: registrations})hasattr(user, doctor)利用了 Django 反向关联的特性如果Doctor模型里用OneToOneField(User)那么 User 实例会自动获得一个.doctor属性。这种判断方式简洁但要注意如果医生账号被删除hasattr会返回 False需要处理边界情况。更稳妥的做法是在User上显式加role字段用choices限定取值范围。3.4 后台管理用 Django admin 快速搭建医生和科室维护Django admin 是这个项目的一大优势。你不需要写任何前端代码只要在admin.py里注册模型就能获得一套功能完整的增删改查后台。# admin.py 注册模型到后台 from django.contrib import admin from .models import Department, Doctor, Schedule, Registration admin.register(Department) class DepartmentAdmin(admin.ModelAdmin): list_display (name, description) search_fields (name,) admin.register(Doctor) class DoctorAdmin(admin.ModelAdmin): list_display (user, department, title, specialty) list_filter (department, title) search_fields (user__username, user__last_name) admin.register(Schedule) class ScheduleAdmin(admin.ModelAdmin): list_display (doctor, date, time_slot, total_slots, remaining_slots) list_filter (date, time_slot, doctor__department) date_hierarchy date admin.register(Registration) class RegistrationAdmin(admin.ModelAdmin): list_display (patient, schedule, status, created_at) list_filter (status, created_at) search_fields (patient__username,)list_display控制列表页显示哪些字段list_filter提供右侧筛选器search_fields开启搜索框date_hierarchy在排班管理里加一个按日期钻取的导航。这些配置加起来不到二十行代码但省掉了大量后台页面的开发时间。答辩时演示后台管理功能直接用 admin 就能撑住场面。4. 避坑与排查跑这份源码时最容易翻车的五个地方4.1 静态文件 404CSS 和 JS 加载不出来现象页面能打开但样式全乱浏览器控制台报GET /static/css/style.css 404。原因Django 在DEBUGTrue时会自动处理静态文件但前提是INSTALLED_APPS里有django.contrib.staticfiles且STATIC_URL配置正确。如果项目把静态文件放在 app 下的static/目录里还需要确保模板里用的是{% static css/style.css %}而不是硬编码路径。解决检查settings.py里STATIC_URL /static/是否存在INSTALLED_APPS是否包含django.contrib.staticfiles。如果静态文件在项目根目录的static/下还要加STATICFILES_DIRS [os.path.join(BASE_DIR, static)]。模板开头记得{% load static %}。4.2 数据库迁移报错Table already exists现象执行python manage.py migrate时提示某张表已存在或者django.db.utils.OperationalError。原因通常是之前跑过一次迁移中途失败或者手动导入过.sql文件建了表但 Django 的迁移记录表django_migrations里没有对应记录。Django 以为没建过表又去建一次就冲突了。解决先确认数据库里到底有哪些表。如果表结构已经和模型一致可以用python manage.py migrate --fake跳过已存在的迁移。如果表结构不对最干净的做法是删库重建DROP DATABASE hospital_registration; CREATE DATABASE hospital_registration CHARACTER SET utf8mb4;然后重新migrate。别在迁移状态混乱的情况下硬改越改越乱。4.3 中文乱码数据库字符集没设对现象页面显示的中文变成????或乱码或者存入数据库的中文查出来是问号。原因MySQL 建库时没指定utf8mb4默认可能是latin1。或者 Django 连接配置里没写OPTIONS: {charset: utf8mb4}。解决建库时强制指定字符集CREATE DATABASE hospital_registration CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;。settings.py的DATABASES里加上OPTIONS: {charset: utf8mb4}。如果表已经建好了可以用ALTER TABLE 表名 CONVERT TO CHARACTER SET utf8mb4;逐张改但不如重建省事。4.4 挂号并发问题两个患者同时抢最后一个号现象测试时发现某个排班的余号变成负数或者两个患者都挂上了同一个号。原因视图函数里先查询余号再扣减这中间有时间窗口。如果两个请求同时进来都查到余号为 1都判断可以挂号然后都扣减就超卖了。解决用数据库层面的锁。Django 里可以用select_for_update()配合事务transaction.atomic def register(request, schedule_id): # 加行锁其他请求会等待 schedule Schedule.objects.select_for_update().get(idschedule_id) if schedule.remaining_slots 0: return render(request, error.html, {msg: 号源已满}) schedule.remaining_slots - 1 schedule.save() Registration.objects.create(patientrequest.user, scheduleschedule) return redirect(registration:my_registrations)select_for_update()会在查询时对行加锁直到事务结束才释放。这样第二个请求会等第一个事务完成后才读到最新的余号。毕设答辩时如果老师问并发问题这就是加分项。4.5 时间字段时区错误挂号时间差八小时现象created_at存的时间比实际时间少八小时或者排班日期判断出错。原因Django 默认用 UTC 时间settings.py里USE_TZ True时auto_now_add存的是 UTC。如果TIME_ZONE没设成Asia/Shanghai显示和存储都会偏。解决settings.py里设TIME_ZONE Asia/Shanghai和USE_TZ True。模板里显示时间时用{{ registration.created_at|date:Y-m-d H:i }}Django 会自动转成当前时区。如果数据库里已经存了错误的时间需要手动修正或重新导入数据。5. 二次开发与毕设加分项从能跑到能讲5.1 加一个「取消挂号」功能完整的前后端改动原始项目如果只有挂号没有取消答辩时容易被问「患者临时有事怎么办」。加这个功能不复杂但能体现你对业务闭环的理解。改动分三处视图、路由、模板。# views.py 取消挂号 login_required transaction.atomic def cancel_registration(request, registration_id): registration get_object_or_404( Registration, idregistration_id, patientrequest.user ) if registration.status ! pending: return render(request, error.html, {msg: 该挂号已处理无法取消}) # 回滚号源 schedule Schedule.objects.select_for_update().get(idregistration.schedule.id) schedule.remaining_slots 1 schedule.save() registration.status cancelled registration.save() return redirect(registration:my_registrations)# urls.py 加路由 path(cancel/int:registration_id/, views.cancel_registration, namecancel_registration),!-- 模板里加按钮 -- {% if reg.status pending %} a href{% url registration:cancel_registration reg.id %} onclickreturn confirm(确认取消挂号)取消挂号/a {% endif %}逻辑说明取消时先判断状态是否为pending已完成的不能取消。然后用select_for_update锁住排班行把remaining_slots加回去。最后把挂号记录状态改成cancelled而不是直接删除记录这样保留操作痕迹方便后台统计。模板里的onclick加一个确认弹窗防止误操作。5.2 用 Django ORM 做统计报表给答辩加一个数据看板答辩时如果只演示增删改查老师可能会觉得工作量不够。加一个简单的统计页面用 ORM 的聚合查询展示「各科室挂号量」「医生接诊排行」代码量不大但视觉效果明显。# views.py 统计看板 from django.db.models import Count, Q from django.utils import timezone user_passes_test(lambda u: u.is_staff) def dashboard(request): # 各科室挂号量 dept_stats Department.objects.annotate( reg_countCount(doctor__schedule__registration, filterQ(doctor__schedule__registration__statuscompleted)) ).values(name, reg_count).order_by(-reg_count) # 今日挂号总数 today timezone.now().date() today_count Registration.objects.filter( created_at__datetoday ).count() # 医生接诊排行前五 doctor_rank Doctor.objects.annotate( completedCount(schedule__registration, filterQ(schedule__registration__statuscompleted)) ).select_related(user).order_by(-completed)[:5] return render(request, admin/dashboard.html, { dept_stats: dept_stats, today_count: today_count, doctor_rank: doctor_rank, })annotate配合Count和filter可以在一次查询里完成分组统计不用写原生 SQL。Q对象用于在聚合时加条件比如只统计状态为completed的挂号。values指定返回哪些字段order_by排序。这个看板页面用 Chart.js 或 ECharts 渲染成柱状图答辩演示效果会好很多。5.3 部署上线从 runserver 到生产环境的最小改动python manage.py runserver只适合开发性能差且不安全。如果毕设要求部署演示可以用gunicornnginx或者更简单的waitress。最小改动方案是改settings.py里的DEBUG False和ALLOWED_HOSTS然后用waitress跑起来。# 安装 waitressWindows 友好 pip install waitress # 启动服务监听 8000 端口 waitress-serve --listen0.0.0.0:8000 hospital_registration.wsgi:applicationDEBUG False后Django 不再自动处理静态文件需要用python manage.py collectstatic把所有静态文件收集到STATIC_ROOT目录然后由 nginx 或 waitress 托管。ALLOWED_HOSTS要填服务器 IP 或域名否则会报DisallowedHost。如果只是本地演示ALLOWED_HOSTS [*]也能用但生产环境别这么写。5.4 答辩时怎么讲这份代码三个技术亮点最后说一个实在的代码跑通了还得能讲清楚。这份项目有三个点可以在答辩时展开。第一用transaction.atomic和select_for_update处理挂号并发体现对数据一致性的理解。第二用 Django ORM 的annotate和Q对象做统计展示对框架的熟练度。第三用user_passes_test装饰器做角色隔离说明权限控制的设计思路。这三个点都不复杂但能把你和「只会 CRUD」的同学区分开。我自己的习惯是每次拿到一份新源码先跑通主流程然后挑一个核心功能把代码读一遍最后试着加一个小功能。这套流程走下来这份代码就真正变成你的了。希望帮到你。本文还有配套的精品资源点击获取