ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Python体检挂号系统源码解析:从环境搭建到二次开发避坑指南

Python体检挂号系统源码解析:从环境搭建到二次开发避坑指南

简介:这是一套面向高校计算机专业学生与Python初学者、用于毕业设计或课程设计场景的医院体检挂号系统完整源码,采用前后台分离的管理模式,前台支持用户注册与个人空间,可查看个人挂号体检记录,后台供管理人员调度预约信息、审核挂号资源,并内置全民体检趋势预测分析算法,帮助医院依据历史数据研判体检需求变化。资源包共268个文件,约8.77MB,以34个py源码文件为核心,辅以31个js、23个css、10个html等前端页面资源,以及gif、jpg、png等界面截图与图标素材,另含sql建库脚本、docx说明文档和pdf参考资料,便于快速还原项目结构。目前已有86人学习下载。整套代码覆盖用户注册、个人空间、挂号管理、数据统计与趋势预测等模块,配合部署说明可完成Python 3.6.8、MySQL 5.7环境下的搭建与调试,适合作为毕业设计参考或二次开发基础。

1. 从一份体检挂号源码说起:它到底解决了医院里的哪个具体麻烦

体检挂号这件事,放在三甲医院是分时段预约、号源池、实名核验、退号改约一整套流程;放在区级医院、社区体检中心、民营体检机构,往往就是一台电脑加一个 Excel,前台护士一边接电话一边手写登记,下午再把纸质表录进表格。这份「基于 Python 的某医院体检挂号系统源代码」瞄准的正是后一种场景——它不是要替代 HIS,而是把体检中心那套「登记—排期—缴费—出报告」的线下动作搬到浏览器里,让前台少写两遍字、让体检人少排一次队。

标题里几个词值得拆开看:Python 说明后端大概率是 Flask 或 Django,源代码说明这是可读可改的完整工程而不是打包好的 exe,毕业设计说明它同时要满足「功能闭环 + 论文可写」两个目标,挂号系统则限定了业务边界。适合谁读?正在做计算机毕业设计、需要一套能跑通、能答辩、能二次开发的同学;也适合小机构里想自己搭一套体检登记工具的技术负责人。下面我按「先跑起来、再看结构、再改业务、最后避坑」的顺序讲,中间会给出可直接抄的命令和参数。

2. 把源码跑起来:环境、依赖与数据库的三步落地

2.1 先判断它是 Flask 还是 Django,再决定装什么

拿到压缩包解压后,别急着pip install,先花两分钟看目录结构。判断依据很直接:根目录下有manage.py和settings.py的,是 Django;有app.py、run.py或requirements.txt里出现 Flask 的,是 Flask。这两种框架的启动方式、数据库迁移命令、模板语法完全不同,装错依赖会浪费一晚上。

我一般会先看requirements.txt,它基本暴露了技术栈。体检挂号系统常见的依赖组合是:Web 框架 + 数据库驱动 + 表单/加密库。下面这份是我在同类项目里最常遇到的依赖清单,你可以对照自己那份做增删:

# 先建虚拟环境,别污染系统 Python python -m venv venv # Windows 激活 venv\Scripts\activate # macOS / Linux 激活 source venv/bin/activate # 安装依赖(以 Flask + MySQL 组合为例) pip install flask flask-sqlalchemy pymysql flask-login flask-wtf pip install pillow openpyxl

逻辑说明:venv隔离环境是为了避免和你机器上已有的 Python 包版本打架,毕业设计项目里Flask版本差异经常导致flask-sqlalchemy报ImportError。pymysql是 MySQL 驱动,如果源码用的是 SQLite,这一行可以省掉。openpyxl通常用于导出体检名单或报告 Excel,pillow用于处理体检人照片或报告图片。

参数说明:flask-sqlalchemy的版本要和flask匹配,Flask 2.3 以上建议配 SQLAlchemy 3.x;如果源码里写的是from flask.ext.sqlalchemy import SQLAlchemy,那是老写法,需要改成from flask_sqlalchemy import SQLAlchemy,这是最常见的翻车点之一。

2.2 数据库配置:改三行就能连上,但字符集必须管

体检挂号系统的数据表通常有:用户表、体检套餐表、号源/排期表、预约记录表、报告表。源码里一般会有一个config.py或settings.py,里面写着数据库连接串。你要改的就是主机、用户、密码、库名这几项。

# config.py 常见结构 import os class Config: # 数据库连接:mysql+pymysql://用户名:密码@主机:端口/库名?charset=utf8mb4 SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \ 'mysql+pymysql://root:123456@127.0.0.1:3306/health_check?charset=utf8mb4' SQLALCHEMY_TRACK_MODIFICATIONS = False SECRET_KEY = 'change-this-in-production'

逻辑说明:charset=utf8mb4不是可选项。体检人姓名里可能有生僻字,体检项目名称里可能有特殊符号,用utf8会在插入时报Incorrect string value。SECRET_KEY用于 session 签名,毕业设计里随便写没关系,但如果真要给机构用,必须换成随机长字符串。

参数说明:SQLALCHEMY_TRACK_MODIFICATIONS = False关掉对象修改追踪,能减少内存开销和警告。端口默认 3306,如果你本机 MySQL 改过端口,这里要同步改。库名health_check需要你先在 MySQL 里CREATE DATABASE health_check DEFAULT CHARSET utf8mb4;建好,否则连接会报Unknown database。

2.3 建表与初始化数据:迁移命令和种子数据

Django 项目用python manage.py migrate,Flask 项目通常用flask db upgrade或者直接执行源码里的init_db.py。体检挂号系统一般会带一个初始化脚本,用来插入管理员账号、几个体检套餐、一周的号源。

# Django 路线 python manage.py makemigrations python manage.py migrate python manage.py createsuperuser # Flask 路线(如果源码带 init_db.py) python init_db.py # 启动服务 python app.py # 或 flask run --host=0.0.0.0 --port=5000

逻辑说明:makemigrations是根据模型生成迁移文件,migrate才是真正建表。很多同学只跑migrate发现表没建,是因为源码里模型改了但没生成迁移文件。--host=0.0.0.0是为了让同一局域网内的手机也能访问,方便你测试移动端页面。

参数说明:端口 5000 是 Flask 默认,Django 是 8000。如果被占用,换 5001 或 8080。启动后浏览器访问http://127.0.0.1:5000,能看到登录页就说明环境通了。如果报ModuleNotFoundError,回到 2.1 检查依赖;如果报数据库连接错误,回到 2.2 检查连接串和 MySQL 服务是否启动。

3. 读懂挂号业务的核心表结构:号源、预约、报告怎么串

3.1 四张主表的关系,决定了你能不能改出「分时段预约」

体检挂号和门诊挂号最大的区别是:门诊挂的是医生,体检挂的是「套餐 + 时间段 + 名额」。所以核心表通常长这样:

表名关键字段作用
userid, username, password_hash, role登录与权限,role 区分管理员/前台/体检人
packageid, name, price, items, duration体检套餐,items 存项目列表
scheduleid, package_id, date, time_slot, capacity, booked号源排期,capacity 是总名额,booked 是已约数
appointmentid, user_id, schedule_id, status, created_at预约记录,status 标记待检/已检/取消

逻辑说明:schedule表是整套系统的咽喉。capacity - booked就是剩余号源,每次预约成功要booked + 1,取消要booked - 1。这个加减操作必须放在数据库事务里,否则两个人同时点预约会把名额超卖。源码里如果只是简单的booked = booked + 1而没有加锁,高并发下会出问题——毕业设计答辩时老师很可能问这一点。

参数说明:time_slot一般存08:00-09:00这样的字符串,或者存开始时间加时长。status建议用枚举值而不是中文,比如pending、done、cancelled,前端再映射成中文显示,这样数据库层面不会因为编码问题出错。

3.2 预约接口的完整逻辑:从点击到写库

下面这段是体检预约接口的典型实现,我把它拆成「校验—扣名额—写记录」三步:

from flask import request, jsonify from models import db, Schedule, Appointment from datetime import datetime @app.route('/api/appointment', methods=['POST']) def make_appointment(): data = request.get_json() user_id = data.get('user_id') schedule_id = data.get('schedule_id') # 第一步:查号源,加行锁防止超卖 schedule = Schedule.query.filter_by(id=schedule_id).with_for_update().first() if not schedule: return jsonify({'code': 404, 'msg': '号源不存在'}), 404 # 第二步:判断剩余名额 if schedule.booked >= schedule.capacity: return jsonify({'code': 400, 'msg': '该时段已约满'}), 400 # 第三步:扣名额 + 写预约记录,同一事务 try: schedule.booked += 1 appt = Appointment( user_id=user_id, schedule_id=schedule_id, status='pending', created_at=datetime.now() ) db.session.add(appt) db.session.commit() return jsonify({'code': 200, 'msg': '预约成功', 'appointment_id': appt.id}) except Exception as e: db.session.rollback() return jsonify({'code': 500, 'msg': '预约失败,请重试'}), 500

逻辑说明:with_for_update()是行级锁,它让同一行号源在事务提交前不能被其他请求修改,这是防超卖的关键。如果没有这一句,两个请求同时读到booked=9、capacity=10,都判断「还有名额」,然后都加 1,结果booked变成 11,超卖。db.session.commit()和rollback()必须成对出现,否则出错后连接会一直挂着。

参数说明:user_id和schedule_id要做类型校验,防止前端传字符串导致查询报错。status='pending'是初始状态,后续体检完成由前台改成done。如果你用的是 SQLite,with_for_update()不支持,需要换成应用层加锁或改用 MySQL/PostgreSQL。

3.3 报告上传与查询:文件存哪、怎么关联

体检报告一般是 PDF 或图片,源码里常见两种存法:存文件路径到数据库,文件放服务器目录;或者直接存二进制到数据库。前者更常见,也更好维护。

import os from werkzeug.utils import secure_filename UPLOAD_FOLDER = 'static/reports' ALLOWED_EXTENSIONS = {'pdf', 'jpg', 'png'} def allowed_file(filename): return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS @app.route('/api/report/upload', methods=['POST']) def upload_report(): file = request.files.get('file') appointment_id = request.form.get('appointment_id') if not file or not allowed_file(file.filename): return jsonify({'code': 400, 'msg': '文件类型不支持'}), 400 filename = secure_filename(f"{appointment_id}_{file.filename}") save_path = os.path.join(UPLOAD_FOLDER, filename) file.save(save_path) # 把路径写回预约记录 appt = Appointment.query.get(appointment_id) appt.report_path = save_path db.session.commit() return jsonify({'code': 200, 'path': save_path})

逻辑说明:secure_filename会过滤掉文件名里的路径分隔符和特殊字符,防止有人上传../../etc/passwd这种恶意文件名。文件名拼上appointment_id是为了避免不同体检人上传同名文件互相覆盖。ALLOWED_EXTENSIONS白名单比黑名单安全,只放行你确定要支持的类型。

参数说明:UPLOAD_FOLDER要确保目录存在且有写权限,否则file.save会报PermissionError。如果部署到 Linux,还要注意static目录的属主。报告路径存相对路径比绝对路径好,迁移服务器时不用改数据。

4. 二次开发与论文加分:三个能拉开差距的改造点

4.1 把「先到先得」改成「分时段限流」

原始源码往往是一个套餐一天一个号源池,改成分时段能显著提升体验。做法是在schedule表里把time_slot拆成独立行,每个时段一行,各自有capacity。前端渲染时按日期分组,后端预约时锁定具体时段行。这个改动涉及表结构和接口,但逻辑不复杂,答辩时能讲清楚「为什么分时段能减少现场拥堵」,是个不错的加分项。

4.2 加一个简单的数据看板

体检中心最关心的是「今天约了多少、哪个套餐最火、取消率多少」。用GROUP BY就能出结果:

-- 今日各套餐预约量 SELECT p.name, COUNT(a.id) AS cnt FROM appointment a JOIN schedule s ON a.schedule_id = s.id JOIN package p ON s.package_id = p.id WHERE DATE(s.date) = CURDATE() AND a.status != 'cancelled' GROUP BY p.name ORDER BY cnt DESC; -- 近 7 天取消率 SELECT DATE(created_at) AS d, SUM(CASE WHEN status='cancelled' THEN 1 ELSE 0 END) / COUNT(*) AS cancel_rate FROM appointment WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL 7 DAY) GROUP BY DATE(created_at);

逻辑说明:第一条按套餐聚合当日有效预约,status != 'cancelled'排除取消记录。第二条算每日取消率,用CASE WHEN做条件计数。这两个查询直接喂给前端图表库就能出图,不需要额外建表。

参数说明:CURDATE()是 MySQL 函数,SQLite 用date('now')。INTERVAL 7 DAY在 SQLite 里要换成date('now', '-7 days')。如果数据量大,给created_at和schedule_id建索引。

4.3 权限控制:别让体检人看到别人的报告

源码里如果只做了登录没做权限,体检人改一下 URL 里的appointment_id就能看到别人的报告,这是答辩时容易被追问的安全问题。最小改动是在查询报告时加上当前用户校验:

@app.route('/api/report/<int:appointment_id>') def get_report(appointment_id): current_user_id = session.get('user_id') appt = Appointment.query.get_or_404(appointment_id) # 管理员可看全部,普通用户只能看自己的 if session.get('role') != 'admin' and appt.user_id != current_user_id: return jsonify({'code': 403, 'msg': '无权查看'}), 403 return jsonify({'code': 200, 'path': appt.report_path})

逻辑说明:get_or_404在记录不存在时直接返回 404,避免后续空指针。权限判断放在业务查询之前,先鉴权再取数据。session里的role在登录时写入,管理员和普通用户走不同分支。

参数说明:appointment_id用<int:...>限定整数,防止传入字符串导致数据库报错。如果源码用的是 JWT 而不是 session,把session.get换成解析 token 里的字段即可。

5. 避坑与排查:这份源码最容易翻车的五个地方

5.1 现象:启动报No module named 'MySQLdb'

原因:源码里写的是mysqlclient或MySQLdb,但你装的是pymysql,两者导入名不同。 解决:在__init__.py或入口文件顶部加两行:import pymysql和pymysql.install_as_MySQLdb(),把 pymysql 伪装成 MySQLdb。或者直接pip install mysqlclient,但 Windows 上编译这个包经常失败,所以伪装法更稳。

5.2 现象:页面能打开但样式全丢,控制台报 404

原因:静态文件路径配置不对,或者你用了flask run但源码里static_folder指向了别的目录。 解决:检查Flask(__name__, static_folder='static', static_url_path='/static')这行,确认static目录真实存在且里面有 css/js。Django 项目则检查STATIC_URL和STATICFILES_DIRS,开发模式下还要在urls.py里加static()辅助函数。

5.3 现象:预约成功但名额没减,或者减了两次

原因:事务没提交,或者提交了两次;也可能是前端重复提交,后端没做幂等。 解决:确认db.session.commit()只调用一次,且在所有修改之后。前端按钮点击后要禁用,防止连点。更稳妥的做法是给预约记录加唯一约束,比如(user_id, schedule_id)唯一,重复插入直接报错,后端捕获后返回「已预约」。

5.4 现象:中文姓名存进数据库变成问号

原因:数据库、表、连接串三处字符集不一致。 解决:建库时DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci,连接串加?charset=utf8mb4,已有表用ALTER TABLE 表名 CONVERT TO CHARACTER SET utf8mb4;转换。三处都对了才不会出问号。

5.5 现象:换台电脑跑不起来,报各种版本冲突

原因:没有锁定依赖版本,requirements.txt里只写了包名没写版本号。 解决:在能跑的机器上执行pip freeze > requirements.txt,把精确版本导出。新机器用pip install -r requirements.txt安装。如果源码自带的requirements.txt没版本号,这是你接手后第一件要补的事。

6. 从能跑到好用:一个把体检挂号系统讲清楚的小技巧

答辩或交付时,最怕的是「功能都做了但说不清业务价值」。我的习惯是准备一张「业务动作—系统动作—数据变化」的对照表,现场演示时按表走,老师或客户问哪一步你都能接住。比如「体检人预约」这个动作,系统里对应的是「查号源—加锁—扣名额—写记录」,数据上schedule.booked加一、appointment新增一行。把这条链路讲顺,比堆十个功能页面都有说服力。

再进一步,你可以给系统加一个「号源日历」页面,用不同颜色标记「可约、紧张、已满」,颜色阈值就是booked / capacity的比例。这个页面不复杂,但视觉冲击强,演示时很抓人。阈值我一般设三档:低于 50% 绿色,50% 到 90% 黄色,90% 以上红色。前端拿到capacity和booked两个数就能算,不需要后端额外接口。

// 前端根据余量算颜色 function slotColor(capacity, booked) { const rate = booked / capacity; if (rate >= 0.9) return '#e74c3c'; // 红:紧张 if (rate >= 0.5) return '#f39c12'; // 黄:一般 return '#27ae60'; // 绿:充足 }

逻辑说明:rate是已约占比,越高越紧张。颜色只是视觉提示,真正的拦截逻辑仍在后端,前端颜色不能作为放行依据。参数说明:阈值 0.5 和 0.9 可以按机构实际情况调,体检旺季可以调低红色阈值,让前台更早介入引导。

最后说个血泪经验:这类毕业设计源码,拿到手第一件事不是改功能,而是先完整跑通一遍、把数据库备份一份、把requirements.txt版本锁死。我见过太多人上来就改代码,改到一半环境崩了,原始版本也回不去,只能重新解压。先跑通、再备份、再动手,这个顺序能帮你省下大量后悔药。希望帮到你。

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

返回列表