资讯动态

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

发布时间:2026/10/26 8:34:48 来源:尧图企业网站定制
简介这是一套面向高校计算机专业学生与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通常用于导出体检名单或报告 Excelpillow用于处理体检人照片或报告图片。参数说明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: # 数据库连接mysqlpymysql://用户名:密码主机:端口/库名?charsetutf8mb4 SQLALCHEMY_DATABASE_URI os.environ.get(DATABASE_URL) or \ mysqlpymysql://root:123456127.0.0.1:3306/health_check?charsetutf8mb4 SQLALCHEMY_TRACK_MODIFICATIONS False SECRET_KEY change-this-in-production逻辑说明charsetutf8mb4不是可选项。体检人姓名里可能有生僻字体检项目名称里可能有特殊符号用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 migrateFlask 项目通常用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 --host0.0.0.0 --port5000逻辑说明makemigrations是根据模型生成迁移文件migrate才是真正建表。很多同学只跑migrate发现表没建是因为源码里模型改了但没生成迁移文件。--host0.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(idschedule_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_iduser_id, schedule_idschedule_id, statuspending, created_atdatetime.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()是行级锁它让同一行号源在事务提交前不能被其他请求修改这是防超卖的关键。如果没有这一句两个请求同时读到booked9、capacity10都判断「还有名额」然后都加 1结果booked变成 11超卖。db.session.commit()和rollback()必须成对出现否则出错后连接会一直挂着。参数说明user_id和schedule_id要做类型校验防止前端传字符串导致查询报错。statuspending是初始状态后续体检完成由前台改成done。如果你用的是 SQLitewith_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 statuscancelled 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_folderstatic, 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连接串加?charsetutf8mb4已有表用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版本锁死。我见过太多人上来就改代码改到一半环境崩了原始版本也回不去只能重新解压。先跑通、再备份、再动手这个顺序能帮你省下大量后悔药。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑