ARTICLE DETAIL

资讯详情

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

互联网医院在线问诊系统全栈开发:PHP后端、前端交互与Shell运维实战

互联网医院在线问诊系统全栈开发:PHP后端、前端交互与Shell运维实战 简介这份源码面向医疗信息化开发者、创业团队及计算机专业学生提供一套基于PHP、HTML、JavaScript、CSS与Shell的互联网医院在线问诊系统实现方案可用于搭建在线问诊、在线开方、在线药房、随访、慢病管理及家庭医生等业务模块并涉及互联网医院牌照申请相关流程适合作为毕业设计、课程项目或中小型医疗平台的开发参考。资源包共2000个文件约160.15MB以1224个HTML页面、409个JavaScript脚本、162个CSS样式表为主另有properties配置、json数据、pdf文档及xml等文件覆盖前端页面、交互逻辑与后端配置等层次。目前已有118人学习下载。整体目录结构完整前端页面与样式资源丰富便于读者快速理解在线问诊系统的模块划分与页面组织方式并在此基础上进行二次开发或功能裁剪。1. 互联网医院在线问诊系统从 PHP 到 Shell 的全栈落地拆解互联网医院在线问诊系统本质是把挂号、图文问诊、视频问诊、处方流转、订单支付这几条业务线用 PHP 做后端接口、HTML/CSS/JavaScript 做前端交互、Shell 做部署运维脚本串起来的一套 Web 应用。它解决的核心问题是让患者不用跑医院就能完成轻问诊让医生在排班时间内批量处理咨询让运营方能在后台看到订单和问诊记录。适合谁做中小型医疗信息化团队、想接医院外包项目的独立开发者、以及需要一套可二次开发问诊系统的产品负责人。这套技术栈不新但胜在部署门槛低、招人容易、改需求快尤其适合预算有限又要快速上线的场景。下面按「先跑通、再调优、后避坑」的顺序把每个技术点落到能复现的步骤上。2. 环境搭建与 PHP 后端骨架让问诊接口先跑起来2.1 PHP 8 运行环境与项目目录约定我一般用 PHP 8.1 起步原因是问诊系统里大量涉及订单金额计算和排班时间比较PHP 8 的match表达式和类型声明能让这些逻辑少写一堆if-else。环境用 Nginx PHP-FPM数据库 MySQL 8缓存 Redis。目录按业务模块切不要按文件类型切否则后期找接口会翻车。# 项目根目录结构Shell 一键生成 mkdir -p /www/ihospital/{public,app/{controller,model,service},config,runtime,script} cd /www/ihospital # public 放入口文件和静态资源app 放业务代码script 放运维脚本逻辑说明public作为 Web 根目录只暴露index.php和static避免源码被直接访问app/controller按Inquiry、Order、Doctor分文件service层放问诊状态机、排班计算这类可复用逻辑。参数上runtime目录必须给 PHP-FPM 运行用户写权限否则日志写不进去接口报 500 却看不到原因。2.2 问诊核心接口的 PHP 实现图文问诊最核心的三个接口是创建问诊单、发送消息、结束问诊。下面用 PHP 8 写创建问诊单的骨架重点看状态字段和事务处理。?php // app/controller/InquiryController.php declare(strict_types1); class InquiryController { public function create(array $params): array { // 参数校验患者ID、医生ID、问诊类型必填 $patientId (int)($params[patient_id] ?? 0); $doctorId (int)($params[doctor_id] ?? 0); $type $params[type] ?? text; // text | video if ($patientId 0 || $doctorId 0) { return [code 400, msg 参数缺失]; } $pdo Db::getInstance(); $pdo-beginTransaction(); try { // 1. 检查医生是否在排班时间内 $stmt $pdo-prepare( SELECT id FROM doctor_schedule WHERE doctor_id ? AND status 1 AND NOW() BETWEEN start_time AND end_time ); $stmt-execute([$doctorId]); if (!$stmt-fetch()) { throw new Exception(医生当前不在排班时间); } // 2. 写入问诊单状态初始为 pending $stmt $pdo-prepare( INSERT INTO inquiry (patient_id, doctor_id, type, status, created_at) VALUES (?, ?, ?, pending, NOW()) ); $stmt-execute([$patientId, $doctorId, $type]); $inquiryId (int)$pdo-lastInsertId(); $pdo-commit(); return [code 0, data [inquiry_id $inquiryId]]; } catch (Exception $e) { $pdo-rollBack(); return [code 500, msg $e-getMessage()]; } } }逻辑说明先校验参数再开事务把「排班检查」和「写问诊单」绑在一起避免医生刚下班还生成单子。参数上status用pending / ongoing / finished / cancelled四态不要用数字后期排查时数字状态是黑匣子。type字段决定前端走图文还是视频通道视频问诊还要额外生成房间号这里先留扩展位。2.3 用 Shell 脚本做一键部署与日志切割问诊系统上线后日志增长很快尤其是消息表。我习惯用 Shell 做两件事部署时拉代码、重启 FPM每天凌晨切割日志。#!/bin/bash # script/deploy.sh 一键部署 set -e APP_DIR/www/ihospital cd $APP_DIR # 拉取最新代码假设用 git 管理 git pull origin main # 安装依赖如果有 composer if [ -f composer.json ]; then composer install --no-dev --optimize-autoloader fi # 重启 PHP-FPM让 opcache 生效 systemctl reload php8.1-fpm echo deploy done at $(date)#!/bin/bash # script/log_rotate.sh 日志切割配合 crontab 每天 3 点执行 LOG_DIR/www/ihospital/runtime/log DATE$(date %Y%m%d) for log in inquiry order doctor; do if [ -f $LOG_DIR/$log.log ]; then mv $LOG_DIR/$log.log $LOG_DIR/${log}_$DATE.log gzip $LOG_DIR/${log}_$DATE.log fi done # 删除 30 天前的日志 find $LOG_DIR -name *.gz -mtime 30 -delete逻辑说明set -e让脚本遇到错误立即停避免半拉子部署。composer install --no-dev不装开发依赖减少线上体积。日志切割用mv gzip不要直接rm出问题时还能翻旧账。参数上-mtime 30表示保留 30 天医疗问诊记录通常要求留存更久这里只切应用日志数据库归档另做。3. 前端问诊界面HTML/CSS/JavaScript 怎么配合才不打架3.1 问诊聊天页的 HTML 结构与 CSS 布局问诊页最怕的是消息气泡错位、输入框被键盘顶飞。HTML 结构要简单一个消息列表容器、一个输入区、一个医生信息栏。!DOCTYPE html html langzh-cn head meta charsetutf-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title在线问诊/title link relstylesheet href/static/css/inquiry.css /head body div classchat-container header classdoctor-bar span classdoctor-name张医生/span span classdoctor-title呼吸内科/span /header main classmsg-list idmsgList/main footer classinput-area textarea idmsgInput placeholder描述您的症状.../textarea button idsendBtn发送/button /footer /div script src/static/js/inquiry.js/script /body /htmlCSS 用 flex 纵向布局msg-list设flex:1; overflow-y:auto;输入区固定在底部。字体用系统默认栈不要引入外部字体问诊场景下加载速度比好看重要。/* static/css/inquiry.css */ .chat-container { display: flex; flex-direction: column; height: 100vh; max-width: 750px; margin: 0 auto; } .msg-list { flex: 1; overflow-y: auto; padding: 12px; background: #f5f6f8; } .input-area { display: flex; padding: 8px; border-top: 1px solid #e0e0e0; } .input-area textarea { flex: 1; resize: none; height: 40px; border: 1px solid #ddd; border-radius: 4px; padding: 8px; }逻辑说明height:100vh让容器占满屏幕flex:1让消息区自动撑开输入区永远在底部。参数上max-width:750px是为了在桌面端也不至于太宽移动端自动适配。注意textarea要设resize:none否则用户拖拽会破坏布局。3.2 JavaScript 消息发送与轮询拉取问诊消息不要求 WebSocket 的场景下用轮询最稳。发送用fetch拉取用setInterval注意判断数据类型和防重复。// static/js/inquiry.js const msgList document.getElementById(msgList); const msgInput document.getElementById(msgInput); const sendBtn document.getElementById(sendBtn); const inquiryId new URLSearchParams(location.search).get(inquiry_id); // 发送消息 async function sendMessage() { const content msgInput.value.trim(); if (!content) return; // 乐观更新先显示在界面 appendMsg({ role: patient, content, time: Date.now() }); msgInput.value ; try { const res await fetch(/api/inquiry/send, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ inquiry_id: inquiryId, content }) }); const data await res.json(); if (data.code ! 0) { alert(发送失败 data.msg); } } catch (e) { alert(网络异常请重试); } } // 拉取新消息每 3 秒一次 let lastId 0; async function pullMessages() { const res await fetch(/api/inquiry/pull?inquiry_id${inquiryId}last_id${lastId}); const data await res.json(); if (data.code 0 Array.isArray(data.data)) { data.data.forEach(msg { appendMsg(msg); lastId msg.id; }); } } function appendMsg(msg) { const div document.createElement(div); div.className msg (msg.role patient ? msg-right : msg-left); div.textContent msg.content; msgList.appendChild(div); msgList.scrollTop msgList.scrollHeight; } sendBtn.addEventListener(click, sendMessage); setInterval(pullMessages, 3000);逻辑说明发送时先乐观更新界面再发请求失败给提示避免用户以为没发出去。拉取用last_id增量拉不要每次全量否则消息一多就卡。参数上轮询间隔 3 秒是问诊场景的平衡点太短费电太长医生回复延迟感明显。appendMsg里用textContent而不是innerHTML防止 XSS。3.3 视频问诊的 JavaScript 调用与降级视频问诊通常接第三方 SDK但要有降级方案检测不到摄像头或 SDK 加载失败时自动切回图文。// 视频问诊初始化带降级 async function initVideo(inquiryId) { try { const stream await navigator.mediaDevices.getUserMedia({ video: true, audio: true }); // 有权限走视频通道 startVideoCall(inquiryId, stream); } catch (e) { // 无权限或设备不支持降级为图文 console.warn(视频不可用降级图文, e); document.querySelector(.video-panel).style.display none; document.querySelector(.chat-container).style.display flex; } }逻辑说明getUserMedia失败的原因很多权限拒绝、没有摄像头、被其他应用占用统一降级最稳。参数上video:true, audio:true是最低要求问诊不需要高清可以在startVideoCall里限制分辨率到 640x480省带宽。4. 问诊业务避坑从订单状态到跨域排查的 5 个血泪记录4.1 现象问诊单状态卡在 pending医生看不到原因创建问诊单和更新医生接诊队列不在同一个事务里高并发时写入了单子但队列没更新。解决把「写 inquiry」和「写 doctor_queue」放同一个beginTransaction里或者用消息队列异步补偿但补偿要有重试上限。4.2 现象前端轮询拉取消息偶尔重复显示原因lastId在异步回调里更新多个请求并发时拿到同一个旧值。解决用let pulling false做锁或者把lastId更新放在appendMsg之前并且用服务端返回的最大 ID 覆盖本地。4.3 现象PHP 接口返回中文乱码原因header(Content-Type: application/json)没带charsetutf-8或者数据库连接没设utf8mb4。解决入口文件统一设header(Content-Type: application/json; charsetutf-8)PDO 连接 DSN 加charsetutf8mb4。4.4 现象Shell 部署脚本执行后网站 502原因systemctl reload php8.1-fpm时 FPM 正在处理请求reload 信号导致子进程退出但新进程没起来。解决用systemctl restart代替reload或者在低峰期部署脚本里加sleep 2再检查systemctl status。4.5 现象前端请求接口报跨域但后端明明加了头原因Nginx 配置里add_header被后续的location覆盖或者 OPTIONS 预检请求没返回 204。解决在 Nginx 的server块统一加 CORS 头并对OPTIONS请求直接return 204不要走到 PHP。5. 问诊系统的进阶技巧用 Shell 做数据归档与接口压测5.1 用 Shell MySQL 做问诊记录冷热分离问诊记录超过半年基本没人查但医疗合规要求留存。我一般用 Shell 脚本每月把inquiry和message表里 6 个月前的数据导出到归档库。#!/bin/bash # script/archive.sh 每月 1 号执行 ARCHIVE_DBihospital_archive SOURCE_DBihospital CUTOFF$(date -d 6 months ago %Y-%m-%d) # 导出旧数据到归档库 mysqldump -h127.0.0.1 -uroot -p$DB_PASS $SOURCE_DB inquiry message \ --wherecreated_at $CUTOFF /tmp/archive_$(date %Y%m).sql mysql -h127.0.0.1 -uroot -p$DB_PASS $ARCHIVE_DB /tmp/archive_$(date %Y%m).sql # 删除源库旧数据谨慎先确认归档成功 mysql -h127.0.0.1 -uroot -p$DB_PASS $SOURCE_DB -e \ DELETE FROM message WHERE created_at $CUTOFF; DELETE FROM inquiry WHERE created_at $CUTOFF;逻辑说明先导出再删除删除前最好人工确认一次。参数上--where条件要加引号否则 Shell 会解析符号。归档库单独放磁盘不要和主库同机避免 IO 争抢。5.2 用 ab 或 wrk 压测问诊接口上线前至少压一次创建问诊单接口看 QPS 和错误率。# 用 ab 压测模拟 100 并发共 1000 次请求 ab -n 1000 -c 100 -p post.json -T application/json \ http://127.0.0.1/api/inquiry/createpost.json里放{patient_id:1,doctor_id:2,type:text}。重点看Failed requests和Time per request。如果失败率高先查 MySQL 连接数再查 PHP-FPM 的pm.max_children是否够。5.3 一个我常犯的错忽略时区问诊排班、消息时间、订单超时都依赖时间。PHP 默认时区可能是 UTCMySQL 也可能是 UTC前端new Date()又是本地时区。我踩过的坑是医生排班 9:00-18:00结果 UTC 下变成 1:00-10:00患者凌晨能挂号。后来统一在 PHP 入口date_default_timezone_set(Asia/Shanghai)MySQL 连接执行SET time_zone08:00前端时间戳统一用毫秒。这个习惯帮我省了很多后悔药。希望帮到你。本文还有配套的精品资源点击获取
返回列表