本文目录导读:

在PHP项目中实现在线问诊功能是一个复杂的系统工程,涉及前端交互、后端业务逻辑、数据安全、实时通信以及医疗合规等多个方面,下面我会为你提供一个从基础到进阶的设计思路和实现步骤。
核心功能模块
一个完整的在线问诊系统通常包含以下模块:
-
用户端
- 图文咨询:发送文字消息、图片(如病历、检查单)。
- 语音/视频咨询:实时音视频通话。
- 问诊记录:查看历史对话、处方、医生建议。
- 支付:支付咨询费。
-
医生端
- 接诊面板:查看待接诊、进行中的患者列表。
- 回复系统:输入文字、发送图片、发送语音、发起视频。
- 开具处方/诊断:填写诊断结果、开具电子处方(药品、用法用量)。
- 患者管理:查看历史患者,回顾历史病历。
-
管理后台
- 医生管理:审核医生资质、排班、上下线。
- 问诊监控:查看实时问诊状态,处理投诉。
- 数据统计:问诊量、收入、用户满意度等。
技术架构选择
关键决策:实时通信方案
这是在线问诊的核心,PHP 是阻塞式的,不适合直接处理长时间的双向连接(如WebSocket),因此你需要选择以下方案之一:
-
方案A:采用第三方服务(推荐)
- 优点:开发成本低,稳定性高,自带合规证书。
- 流行选项:
- 腾讯云IM(即时通信):集成简单,支持文字、图片、语音、视频。
- 声网(Agora):专注音视频领域,延迟极低。
- 环信、融云:成熟的IM/PaaS平台。
- 工作流程:
- 你的PHP后端生成通信所需的 Token/AppKey。
- 前端(H5/App)使用SDK直接连接第三方服务器进行实时通信。
- PHP后端通过第三方提供的回调或REST API 获取聊天记录、通话时长等数据并存储到自己的数据库。
-
方案B:自建WebSocket服务 + Swoole/Workerman
- 原理:PHP 7/8配合Swoole扩展或Workerman框架,常驻内存,性能接近Go/Java。
- 优点:完全控制数据,长期来看成本可控。
- 缺点:开发复杂度高,需要对Swoole/Workerman有深入了解,需要处理高并发下的连接管理。
- 实现:PHP后端作为WebSocket服务器,前端建立WebSocket连接,PHP负责转发消息、存储聊天记录。
存储与业务逻辑
- PHP框架:Laravel、ThinkPHP、Symfony 等。
- 数据库:
- MySQL:存储用户信息、订单、病历、药品库。
- Redis:缓存医生在线状态、未读消息数、会话锁。
- 文件存储:阿里云OSS、腾讯云COS 或 本地分布式存储。不要存图片到数据库。
数据库表设计 (核心表)
-- 问诊订单表
CREATE TABLE `consultation_orders` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`order_sn` varchar(32) NOT NULL COMMENT '订单号',
`user_id` bigint unsigned NOT NULL COMMENT '患者ID',
`doctor_id` bigint unsigned NOT NULL COMMENT '医生ID',
`status` tinyint NOT NULL DEFAULT '0' COMMENT '状态: 0待支付, 1待接诊, 2进行中, 3已完成, 4已取消',
`type` tinyint NOT NULL COMMENT '类型: 1图文, 2语音, 3视频',
`amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '金额',
`start_time` datetime DEFAULT NULL COMMENT '接诊时间',
`end_time` datetime DEFAULT NULL COMMENT '结束时间',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_doctor_id` (`doctor_id`),
KEY `idx_status` (`status`)
);
-- 消息记录表 (如果自建IM)
CREATE TABLE `chat_messages` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`order_id` bigint unsigned NOT NULL COMMENT '会话ID',
`sender_type` tinyint NOT NULL COMMENT '发送者类型: 1患者, 2医生',
`msg_type` tinyint NOT NULL COMMENT '消息类型: 1文本, 2图片, 3语音, 4视频',
`content` text COMMENT '文本内容 或 文件URL',
`duration` int DEFAULT '0' COMMENT '音频/视频时长(秒)',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_order_id` (`order_id`)
);
-- 处方表
CREATE TABLE `prescriptions` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`order_id` bigint unsigned NOT NULL,
`doctor_id` bigint unsigned NOT NULL,
`diagnosis` text COMMENT '诊断结果',
`advice` text COMMENT '医师建议',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
);
-- 处方明细表
CREATE TABLE `prescription_items` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`prescription_id` bigint unsigned NOT NULL,
`drug_name` varchar(100) NOT NULL COMMENT '药品名称',
`specification` varchar(50) COMMENT '规格',
`dosage` varchar(50) COMMENT '用量',
`frequency` varchar(50) COMMENT '频次',
`days` tinyint COMMENT '天数',
`quantity` int COMMENT '数量',
PRIMARY KEY (`id`)
);
核心流程实现 (以“图文咨询 + 第三方IM”为例)
创建问诊订单
// ConsultationController.php (Laravel 示例)
public function createOrder(Request $request) {
// 1. 验证参数
$userId = auth()->id();
$doctorId = $request->input('doctor_id');
$type = $request->input('type', 1); // 1=图文
// 2. 检查医生在线状态 (从Redis获取)
$doctorOnline = Redis::get('doctor:online:'.$doctorId);
if (!$doctorOnline) {
return response()->json(['error' => '医生当前不在线'], 400);
}
// 3. 生成订单(状态为待支付)
$order = ConsultationOrder::create([
'order_sn' => generateOrderSn(),
'user_id' => $userId,
'doctor_id' => $doctorId,
'type' => $type,
'status' => 0, // 待支付
'amount' => $doctor->consultation_fee,
]);
// 4. 生成支付凭证 (调用支付网关)
// ...
return response()->json(['order' => $order, 'payment_url' => $payUrl]);
}
支付成功后,分配会话
// PaymentController.php (支付回调)
public function paymentCallback(Request $request) {
// 1. 验证签名
// 2. 更新订单状态为 "待接诊"
$order = ConsultationOrder::where('order_sn', $request->order_sn)->first();
$order->status = 1;
$order->save();
// 3. 通过第三方IM创建会话 (例如使用腾讯云IM)
// 调用腾讯云IM API:创建一个新的单聊会话
// 参数:from_account = 系统管理员,to_account = 医生ID,会话组 = 患者ID
$imService->createConversation($order->user_id, $order->doctor_id);
// 4. 通知医生有新订单 (通过WebSocket 或 推送)
event(new NewConsultationEvent($order));
// 5. 生成通信Token
$userSig = $imService->genUserSig($order->user_id);
$doctorSig = $imService->genUserSig($order->doctor_id);
// 返回给前端
}
前端接入第三方IM
// 前端 (JS SDK 示例)
// 1. 初始化 SDK
let tim = TIM.create({
SDKAppID: YOUR_SDK_APP_ID
});
// 2. 登录 (用户点击开始咨询时)
tim.login({
userID: userId,
userSig: userSig // 从PHP后端获取
});
// 3. 获取会话列表
let conversation = tim.getConversationList();
// 4. 发送消息 (用户输入并点击发送)
let message = tim.createTextMessage({
to: doctorUserId,
conversationType: TIM.TYPES.CONV_C2C,
payload: { text: '医生您好,我最近经常头痛...' }
});
tim.sendMessage(message);
// 5. 接收消息 (监听事件)
tim.on(TIM.EVENT.MESSAGE_RECEIVED, function(event) {
// 新消息到达,更新聊天界面
});
医生开具处方
// DoctorPrescriptionController.php
public function createPrescription(Request $request, $orderId) {
$order = ConsultationOrder::findOrFail($orderId);
// 验证医生身份
if ($order->doctor_id != auth()->id()) {
abort(403);
}
// 创建处方主表
$prescription = Prescription::create([
'order_id' => $orderId,
'doctor_id' => auth()->id(),
'diagnosis' => $request->input('diagnosis'),
'advice' => $request->input('advice'),
]);
// 创建处方明细
foreach ($request->input('drugs') as $drug) {
$prescription->items()->create($drug);
}
// 更新订单状态为 "已完成"
$order->status = 3; // 已完成
$order->end_time = now();
$order->save();
// 通知患者 (推送)
return response()->json(['prescription' => $prescription->load('items')]);
}
必须注意的关键问题
-
数据安全与合规
- 问诊数据是敏感医疗数据。必须使用HTTPS 传输。
- 数据库中的问诊记录、处方、聊天记录等应做加密存储(字段级加密或全库加密)。
- 严格遵守当地医疗数据保护法规(如中国的《互联网诊疗管理办法》),问诊记录需保留至少15年。
-
医生资质核验
- 上线前必须强制医生上传《医师执业证》、《医师资格证》。
- 后台有严格的审核流程,确保“人证合一”。
-
责任划分
- 清晰的用户协议,界定“在线问诊仅提供健康咨询,不能替代线下诊疗”。
- 处方药需要更强的审核机制(如医生电子签名)。
-
性能优化
- 冷热数据分离:将最近1个月的问诊记录放在MySQL热库,历史数据定期迁移到归档库或ES。
- 缓存:医生排班、在线状态、常见问答等缓存到Redis。
-
应急处理
- 断线重连:音视频或IM服务需要实现牢固的重连机制。
- 超时未接诊:如果医生5分钟未接诊,系统自动取消订单并全额退款。
总结与推荐
-
学习成本最低、最稳定的方案:PHP(Laravel)+ 第三方IM/音视频服务(腾讯云IM/声网)。
- PHP专注于处理订单、用户、支付、处方这些业务逻辑。
- 实时通信交给专业的第三方平台,你只需要几个API调用就搞定了。
-
如果想自己秀肌肉或成本极致压缩:PHP(Laravel) + Workerman/Swoole + WebSocket。
- 你自己实现消息的路由、持久化、推送。
- 注意:这会消耗大量时间和Bug调试,除非你的团队有Swoole专家,否则不推荐自建IM。
额外建议:不要试图在一个Query里查所有聊天记录,分页加载,一定要做好图片、语音文件的CDN加速,否则医生打开患者发的检查单图片会非常慢。
如果需要更具体的代码片段或某个模块的深入讲解,可以告诉我你卡在哪一步。