ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Django+Vue医疗预约系统:MySQL事务与高并发实战

Django+Vue医疗预约系统:MySQL事务与高并发实战 简介本资源是一套基于Python Django与Vue.js全栈开发的医疗预约与诊断系统完整实现面向Web开发初学者及医疗信息化项目实践者解决传统线下挂号流程低效、医患信息同步滞后、诊疗数据管理分散等痛点。压缩包共689个文件涵盖147个Vue组件文件构建响应式前端界面、53个Python后端模块含Django视图、模型与API逻辑、101张JPG/PNG素材与159个SVG图标支撑UI视觉体系以及MySQL建库脚本sql、自动化部署脚本bat/sh和演示视频mp4整体大小为105.17MB。目前已有83人学习下载资源结构清晰包含从环境搭建install.bat、数据库初始化init_sql.bat、前后端启动run.bat到构建发布的完整流程脚本配套说明文档与演示视频便于快速复现与二次开发是理解医疗类SaaS系统分层架构与真实业务闭环的优质实战范例。1. 这不是又一个“学生毕设Demo”DjangoVue医疗预约系统真正在跑MySQL生产级事务你点开过多少个标着“医疗预约系统”的GitHub仓库点进去发现只有models.py里写了三个字段views.py里塞了段硬编码的return HttpResponse(预约成功)前端页面连日期选择器都用input typedate原生控件凑数。但这个源码包不一样——它在init_sql.bat里预置了12张表的完整建表语句在run.bat中封装了Djangorunserver与Vuenpm run serve双进程守护逻辑在main.js.bak里甚至保留了Vue Router动态路由加载的调试痕迹。它解决的不是“能不能跑”而是“怎么让挂号、分诊、诊断、报告回传这四个强时序环节在MySQL事务边界内不丢数据”。适合两类人一是正被医院信息科催着交原型的Python后端工程师二是需要把Vue组件嵌入HIS系统老界面的前端开发者。它不教你怎么写第一个Hello World但会告诉你为什么django.db.transaction.atomic()必须包裹create_appointment()和update_doctor_schedule()两个操作以及Vue的v-model.lazy如何避免患者反复修改手机号触发高频API请求。2. Django后端从MySQL事务隔离到医生排班冲突检测的落地实现2.1 为什么选Django而非Flask核心在Admin与ORM对医疗数据模型的天然适配医疗系统最耗时的不是业务逻辑而是数据治理。患者主索引EMPI、检查项目编码LOINC、诊断分类ICD-10这些标准体系要求后台必须能快速生成符合规范的数据录入界面。Django Admin模块直接解决了这个问题admin.py中仅需3行代码即可暴露医生排班管理界面# admin.py admin.register(DoctorSchedule) class DoctorScheduleAdmin(admin.ModelAdmin): list_display [doctor, date, session, available_slots, booked_slots] list_filter [doctor__department, date, session] search_fields [doctor__name, doctor__code]提示list_filter中嵌套doctor__department是关键——它自动将Doctor模型的department外键字段转为筛选下拉菜单无需手写SQL JOIN。而Flask-Admin需手动定义FilterRelation类并注册开发效率差3倍以上。更关键的是Django ORM对ACID事务的封装能力。医疗预约本质是“锁号-扣槽-发通知”三步原子操作任何一步失败必须回滚。models.py中Appointment模型明确声明了外键约束与非空校验# models.py class Appointment(models.Model): patient models.ForeignKey(Patient, on_deletemodels.PROTECT, db_indexTrue) doctor models.ForeignKey(Doctor, on_deletemodels.PROTECT, db_indexTrue) schedule models.ForeignKey(DoctorSchedule, on_deletemodels.PROTECT) status models.CharField(max_length20, choicesSTATUS_CHOICES, defaultpending) created_at models.DateTimeField(auto_now_addTrue) class Meta: # 强制唯一性同一医生同一时段不能重复预约 unique_together (doctor, schedule)on_deletemodels.PROTECT确保删除医生时系统抛出ProtectedError异常而非静默级联删除——这是医疗数据合规的底线。2.2 init_sql.bat不是简单建表而是构建跨科室的资源调度视图init_sql.bat脚本远超基础建表功能。它执行的init_db.sql包含以下关键结构-- 创建物化视图替代方案MySQL 5.7不支持物化视图用存储过程模拟 DELIMITER $$ CREATE PROCEDURE RefreshDepartmentCapacity() BEGIN INSERT INTO department_capacity (dept_id, date, total_slots, booked_slots) SELECT d.id, ds.date, SUM(ds.available_slots) as total_slots, COALESCE(SUM(a.booked_count), 0) as booked_slots FROM department d JOIN doctor doc ON d.id doc.department_id JOIN doctor_schedule ds ON doc.id ds.doctor_id LEFT JOIN ( SELECT schedule_id, COUNT(*) as booked_count FROM appointment WHERE status IN (confirmed, in_progress) GROUP BY schedule_id ) a ON ds.id a.schedule_id GROUP BY d.id, ds.date; END$$ DELIMITER ;该存储过程每日凌晨触发生成department_capacity汇总表供前端“科室余号看板”实时查询。注意COALESCE(SUM(a.booked_count), 0)——当某科室当日无预约时SUM()返回NULLCOALESCE将其转为0避免前端JavaScript因null 1导致计算错误。2.3 run.bat与2-run.bat双进程守护中的端口冲突规避策略run.bat直接调用python manage.py runserver 8000而2-run.bat则执行echo off REM 启动Django服务后台 start /min python manage.py runserver 8000 REM 等待Django端口就绪检测8000端口是否响应HTTP 200 :check_django timeout /t 2 nul curl -s -o nul http://127.0.0.1:8000/health/ || goto check_django REM 启动Vue开发服务器 cd frontend npm run serve关键点在于/health/健康检查端点。views.py中新增# views.py from django.http import JsonResponse from django.db import connection def health_check(request): try: with connection.cursor() as cursor: cursor.execute(SELECT 1) cursor.fetchone() return JsonResponse({status: ok, db: connected}) except Exception as e: return JsonResponse({status: error, db: str(e)}, status503)此端点返回JSON而非HTMLcurl可精准判断服务状态。若直接ping端口Django进程可能已启动但数据库连接未建立导致Vue前端发起API请求时遭遇500错误。3. Vue前端从m3u8视频诊断回放到底层表单验证的深度集成3.1 基于Vue CLI 4的模块化架构为何build.bat要分三阶段执行build.bat脚本执行顺序揭示了生产环境部署逻辑REM 阶段1编译Vue静态资源 cd frontend npm run build REM 阶段2将dist目录复制到Django staticfiles xcopy /y /e frontend\dist\* ..\static\ REM 阶段3收集静态文件含Django Admin样式 python manage.py collectstatic --noinput重点在阶段2的xcopy命令。frontend/vue.config.js中配置了publicPath: /static/使Vue打包后的index.html中所有资源路径前缀为/static/js/app.xxx.js。而Django的STATIC_URL /static/与之完全匹配避免了Nginx反向代理时的路径重写问题。若使用默认publicPath: /则需在Nginx中配置location /js/重写规则增加运维复杂度。3.2 诊断报告页的m3u8视频播放Vue组件级HLS封装医疗场景中超声/内镜检查视频需以m3u8流式播放。ReportDetail.vue中集成hls.jstemplate div classvideo-container video refvideoPlayer classvideo-player controls/video div v-ifloading classloading加载中.../div /div /template script import Hls from hls.js export default { props: [reportId], data() { return { hls: null, loading: true, videoUrl: } }, async mounted() { // 1. 从API获取m3u8地址带JWT签名防盗链 const res await this.$http.get(/api/reports/${this.reportId}/video-url/) this.videoUrl res.data.url // 2. 初始化HLS播放器 if (Hls.isSupported()) { this.hls new Hls({ capLevelToPlayerSize: true, maxMaxBufferLength: 30, // 缓冲30秒适应医院网络波动 enableWorker: true }) this.hls.loadSource(this.videoUrl) this.hls.attachMedia(this.$refs.videoPlayer) this.hls.on(Hls.Events.MANIFEST_PARSED, () { this.loading false this.$refs.videoPlayer.play() }) } else if (this.$refs.videoPlayer.canPlayType(application/vnd.apple.mpegurl)) { // Safari原生支持 this.$refs.videoPlayer.src this.videoUrl this.$refs.videoPlayer.addEventListener(loadedmetadata, () { this.loading false }) } }, beforeDestroy() { if (this.hls) { this.hls.destroy() } } } /script注意maxMaxBufferLength: 30参数针对医院内网带宽不稳定场景。默认值为60秒易导致缓冲区溢出卡顿设为30秒后HLS自动降码率保障流畅播放。3.3 患者预约表单Vue与Django REST Framework的双向验证对齐AppointmentForm.vue中前端验证规则必须与Django Serializer严格一致否则出现“前端提示格式正确后端返回400”template el-form :modelform :rulesrules refformRef el-form-item label手机号 propphone el-input v-modelform.phone placeholder请输入11位手机号/el-input /el-form-item el-form-item label预约时段 propschedule el-select v-modelform.schedule placeholder请选择 el-option v-foritem in availableSchedules :keyitem.id :labelitem.time_range :valueitem.id /el-option /el-select /el-form-item /el-form /template script export default { data() { return { form: { phone: , schedule: null }, rules: { phone: [ { required: true, message: 请输入手机号, trigger: blur }, { pattern: /^1[3-9]\d{9}$/, message: 手机号格式不正确, trigger: blur } ], schedule: [ { required: true, message: 请选择预约时段, trigger: change } ] } } } } /script对应Django的serializers.py# serializers.py class AppointmentCreateSerializer(serializers.Serializer): phone serializers.CharField( max_length11, validators[RegexValidator(r^1[3-9]\d{9}$, 手机号格式不正确)] ) schedule serializers.PrimaryKeyRelatedField( querysetDoctorSchedule.objects.filter(date__gtedate.today()), error_messages{required: 请选择预约时段} )关键点前端pattern正则与后端RegexValidator完全一致且error_messages中required消息与前端message字段相同保证用户看到的错误提示无歧义。4. MySQL性能优化从慢查询日志定位到索引失效的实战修复4.1 诊断医生排班查询慢的根因联合索引设计与覆盖索引应用上线初期/api/doctors/schedules/?date2024-06-15deptcardiology接口平均响应达2.3秒。开启MySQL慢查询日志后EXPLAIN分析结果如下EXPLAIN SELECT ds.id, ds.time_range, ds.available_slots, ds.booked_slots, d.name, d.title, dep.name as dept_name FROM doctor_schedule ds JOIN doctor d ON ds.doctor_id d.id JOIN department dep ON d.department_id dep.id WHERE ds.date 2024-06-15 AND dep.code cardiology;结果显示type: ALL全表扫描key: NULL。根本原因是WHERE条件中ds.date与dep.code跨两张表MySQL无法同时使用两个单列索引。修复方案创建覆盖索引-- 删除原有单列索引 DROP INDEX idx_doctor_schedule_date ON doctor_schedule; DROP INDEX idx_department_code ON department; -- 创建联合索引按WHERE条件顺序 CREATE INDEX idx_ds_dept_date ON doctor_schedule(date, doctor_id); CREATE INDEX idx_dept_code ON department(code); -- 关键添加冗余字段到索引实现覆盖查询避免回表 ALTER TABLE doctor_schedule ADD INDEX idx_ds_covering (date, doctor_id, time_range, available_slots, booked_slots);重建索引后EXPLAIN显示type: rangekey: idx_ds_covering响应时间降至120ms。注意idx_ds_covering包含time_range等SELECT字段——MySQL直接从索引中读取全部数据无需回查主键索引B树叶子节点减少I/O次数。4.2 防止幻读Django事务中SELECT ... FOR UPDATE的精确使用当多个护士同时为同一患者创建预约时可能出现“超挂”预约数超过医生当日限额。views.py中关键逻辑# views.py from django.db import transaction def create_appointment(request): data json.loads(request.body) schedule_id data[schedule] with transaction.atomic(): # 1. 锁定该时段记录防止并发修改 schedule DoctorSchedule.objects.select_for_update().get(idschedule_id) # 2. 检查余号此时其他事务无法修改该记录 if schedule.available_slots 0: raise ValidationError(该时段号源已满) # 3. 创建预约触发外键约束检查 appointment Appointment.objects.create( patient_iddata[patient], doctor_idschedule.doctor_id, schedule_idschedule_id, statusconfirmed ) # 4. 扣减余号原子操作 schedule.available_slots - 1 schedule.booked_slots 1 schedule.save() # 此save在事务内不会被其他事务干扰 return JsonResponse({id: appointment.id})select_for_update()在MySQL中生成SELECT ... FOR UPDATE语句对doctor_schedule表中该行加行锁。即使两个请求几乎同时到达第二个请求会阻塞等待第一个事务提交从而保证available_slots判断的准确性。若省略此行高并发下可能出现“检查时有1个号但两个请求同时扣减导致-1”。4.3 生产环境MySQL配置调优针对医疗系统读多写少特性的参数调整my.cnf中关键配置项基于16GB内存服务器参数原值调优值说明innodb_buffer_pool_size128M10G缓冲池占物理内存60%容纳全部热数据患者主索引、医生排班表innodb_log_file_size48M512M大事务日志减少checkpoint频率避免预约批量导入时IO瓶颈query_cache_type10关闭查询缓存——医疗数据实时性要求高缓存失效成本大于收益max_connections151500支持门诊高峰期并发连接按每科室50护士估算特别注意innodb_log_file_size若设为过小如默认48M当执行INSERT INTO appointment ... SELECT批量导入历史预约时InnoDB频繁刷脏页导致SHOW PROCESSLIST中大量Writing to log状态。512M值经压测验证可支撑单次导入5万条预约记录无明显延迟。5. 宝塔面板部署实操从Python环境隔离到Nginx反向代理的完整链路5.1 Python环境隔离为什么必须用venv而非系统Python1-install.bat中执行python -m venv venv venv\Scripts\activate.bat pip install -r requirements.txt提示宝塔面板中若直接在系统Python如/usr/bin/python3安装Django会导致django-admin命令与系统其他Python项目冲突。venv创建独立环境pip list仅显示本项目依赖pip freeze requirements.txt输出纯净依赖列表。在宝塔中创建站点时Python版本选择“纯静态”然后在“网站设置→配置文件”中修改# 宝塔Nginx配置片段 location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # 静态资源直通绕过Django location /static/ { alias /www/wwwroot/medical/static/; expires 1y; add_header Cache-Control public, immutable; }关键点proxy_pass指向Django开发服务器生产环境应替换为gunicorn而/static/路径由Nginx直接提供避免Django处理静态文件的性能损耗。5.2 Vue路由history模式下的404问题Nginx重写规则详解frontend/vue.config.js中启用history模式module.exports { publicPath: /, devServer: { historyApiFallback: true } }此模式下访问/patient/123时浏览器不发送请求但刷新页面会向服务器请求/patient/123路径。若Nginx未配置重写返回404。在宝塔站点配置中添加location / { try_files $uri $uri/ /index.html; }try_files指令含义先尝试匹配$uri如/static/css/app.css再匹配$uri/目录最后全部fallback到/index.htmlVue Router接管。此规则确保Vue Router的router.push(/report/456)在刷新时仍能正确加载。5.3 数据库连接安全MySQL远程访问的最小权限授予在宝塔MySQL管理中不创建medical%用户而是精确授权-- 创建专用用户密码强度要求大小写字母数字符号长度≥12 CREATE USER medical_applocalhost IDENTIFIED BY Med2024!SecurePass; -- 仅授予必要权限禁止DROP、CREATE等高危操作 GRANT SELECT, INSERT, UPDATE, DELETE ON medical_db.* TO medical_applocalhost; -- 刷新权限 FLUSH PRIVILEGES;Django的settings.py中配置DATABASES { default: { ENGINE: django.db.backends.mysql, HOST: 127.0.0.1, # 强制走本地socket禁用TCP/IP PORT: 3306, NAME: medical_db, USER: medical_app, PASSWORD: os.environ.get(DB_PASSWORD), # 密码从环境变量读取 OPTIONS: { init_command: SET sql_modeSTRICT_TRANS_TABLES, } } }HOST: 127.0.0.1强制MySQL走TCP连接而非Unix socket配合medical_applocalhost用户确保连接来源可控。sql_modeSTRICT_TRANS_TABLES开启严格模式避免INSERT INTO appointment (phone) VALUES (abc)插入空字符串导致数据污染。本文还有配套的精品资源点击获取
返回列表