
前言:为什么要做视频面诊
2020 年以来,国家卫健委陆续出台政策,明确互联网医院可开展部分常见病、慢性病的复诊服务,视频问诊从”灰色地带”走向合规主流。2022 年《互联网诊疗监管细则》进一步规范了平台资质和诊疗行为,给了行业一个清晰的合规框架。
哪些科室最适合视频面诊?
| 科室 | 适合原因 |
|---|---|
| 皮肤科 | 皮损可视化,医生通过高清视频即可初步判断 |
| 中医望诊 | 面色、舌苔、神态是核心诊断依据 |
| 慢病复诊 | 高血压、糖尿病患者定期随访,无需每次到院 |
| 心理咨询 | 面部表情和肢体语言是重要的诊断信息 |
| 儿科初诊 | 减少交叉感染风险,家长在家即可完成初步问诊 |
为什么选微信小程序而不是 APP?
- 免安装,患者打开微信即用,转化率远高于 APP。
- 微信生态内天然具备社交传播能力。
- 开发和维护成本低于双端原生 APP。
- 微信支付、实名认证等基础能力开箱即用。
核心能力拆解:视频面诊需要哪些技术模块
很多开发者一开始把视频面诊理解成”接个视频通话”,上线后才发现坑远不止于此。完整的视频面诊系统一般需要以下模块:
| 模块 | 核心功能 | 技术复杂度 |
|---|---|---|
| 实时音视频通话 | 医患双向视频,低延迟、抗弱网 | ★★★ |
| 候诊排队系统 | 叫号、等待状态推送、超时处理 | ★★ |
| 身份核验 | 医生资质认证 + 患者实名制 | ★★ |
| 电子病历联动 | 问诊前填写主诉、诊后记录处方 | ★★ |
| 录制与存档 | 合规要求的诊疗录像保存(境内服务器) | ★★★ |
| 消息通知 | 叫号提醒、问诊结束通知 | ★ |
本文聚焦最核心的实时音视频通话模块,其余模块可在此基础上逐步叠加。
技术选型:实时音视频方案怎么选
微信小程序的实时音视频方案主要有三条路:
方案 A:微信原生音视频通话组件
- 原生组件,需申请特定类目资质。
- 限制多(仅支持特定场景)、审核严,但接入后体验最原生。
- 适合:已有微信生态深度合作的大型医疗机构。
方案 B:接入即构 ZEGO 实时音视频 SDK
- 跨平台能力强,同一套业务逻辑可复用到 iOS/Android/Web等平台。
- 微信小程序端提供专用 SDK,封装了
<zego-pusher>/<zego-player>组件,屏蔽了原生组件的复杂性。 - 全球平均端到端延迟 200ms,支持 80% 丢包环境下的流畅通话。
- 提供远程医疗场景的完整解决方案参考。
方案 C:腾讯云实时音视频(TRTC)
- 与微信小程序原生集成最顺畅,底层共用腾讯基础设施。
- 使用
<live-pusher>+<live-player>原生组件。 - 文档完善,腾讯云全家桶用户迁移成本低。
三方案对比
| 维度 | TRTC | 微信原生 | ZEGO |
|---|---|---|---|
| 延迟 | 低(<300ms) | 低 | 低(<200ms) |
| 费用 | 按分钟计费 | 免费(限制多) | 按分钟计费 |
| 接入难度 | 中等 | 高(类目限制严) | 低 |
| 跨平台复用 | 一般 | 不支持 | 好 |
| 合规录制支持 | 好 | 一般 | 好 |
| 弱网优化 | 好 | 一般 | 好 |
本文以 ZEGO 作为实现方案,以下为核心实现步骤。
关键实现步骤(基于即构 ZEGO SDK)
前提条件
注册即构账号,创建项目
- 前往ZEGO 控制台注册账号;
- 创建项目,申请有效的 AppID 和 AppSign;
- 开通实时音视频(RTC)服务
小程序类目申请(必须先做)
微信小程序使用音视频推拉流组件,需满足:
| 条件 | 说明 |
|---|---|
| 小程序类目 | 医疗 > 互联网医院 / 在线问诊 |
| 所需资质 | 互联网医院牌照 或 卫健委备案证明 |
| 组件权限 | 在微信公众平台后台手动开启”实时播放音视频流”和”实时录制音视频流” |
⚠️ 未开通类目直接调用会静默失败,是最常见的踩坑点。开发阶段可用测试号绕过,但上线前必须完成类目审核。
配置微信小程序后台域名白名单
在”小程序后台 > 开发管理 > 开发设置 > 服务器域名”中,将 ZEGO 的 Server 地址和 LogUrl 填入 request 合法域名 和 socket 合法域名。具体地址在 ZEGO 控制台的项目信息页获取。
集成 ZEGO 小程序实时音视频 SDK
ZEGO 提供专用的微信小程序 SDK,推荐通过 npm 安装:
npm install zego-express-engine-miniprogram
安装后在微信开发者工具菜单栏选择”工具 > 构建 npm”,并勾选”使用 npm 模块”。
在页面 JS 文件顶部引入:
import { ZegoExpressEngine } from "zego-express-engine-miniprogram";
同时,将官方示例代码中的 components/zego-pusher 和 components/zego-player 两个组件文件夹复制到项目的 components 目录下(示例源码地址)。
后端生成 Token(安全鉴权)
Token 必须由服务端生成,不能在前端硬编码 AppSign。每次用户进入问诊房间前,前端向后端请求一个有时效的 Token。
Node.js 示例(后端):
const crypto = require('crypto');
function generateToken04(appId, userId, serverSecret, expireTime) {
const expire = Math.floor(Date.now() / 1000) + expireTime;
const nonce = Math.floor(Math.random() * 2147483647);
const ctime = Math.floor(Date.now() / 1000);
const plaintext = `${appId}${userId}${nonce}${ctime}${expire}`;
const hmac = crypto.createHmac('sha256', serverSecret);
hmac.update(plaintext);
const hash = hmac.digest('hex');
const tokenInfo = {
ver: 4,
hash,
expire,
nonce,
ctime,
};
const tokenStr = Buffer.from(JSON.stringify(tokenInfo)).toString('base64');
return `04${tokenStr}`;
}
// 接口:前端调用获取 Token
app.get('/api/token', (req, res) => {
const { userId } = req.query;
const token = generateToken04(
YOUR_APP_ID,
userId,
YOUR_SERVER_SECRET, // 控制台获取,绝不能暴露给前端
3600 // 1小时有效
);
res.json({ token });
});
注意:控制台提供临时 Token 生成工具,方便开发调试,但生产环境必须走自己的服务端。
初始化 ZEGO 引擎
在问诊页面的 JSON 文件中引入组件:
{
"usingComponents": {
"zego-pusher": "../../components/zego-pusher/zego-pusher",
"zego-player": "../../components/zego-player/zego-player"
}
}
在页面 JS 的 onLoad 中初始化引擎:
import { ZegoExpressEngine } from "zego-express-engine-miniprogram";
Page({
data: {
pusher: {}, // 推流属性,由 SDK 管理
playerList: [], // 拉流列表,由 SDK 管理
zegoPlayerList: [],
},
async onLoad(options) {
const { roomId, userId } = options;
// 1. 从后端获取 Token
const res = await new Promise(resolve =>
wx.request({ url: `https://your-server.com/api/token?userId=${userId}`, success: resolve })
);
const token = res.data.token;
// 2. 创建引擎实例(appID 为数字,server 为控制台获取的 Server 地址)
const zg = new ZegoExpressEngine(YOUR_APP_ID, YOUR_SERVER);
// 3. 绑定页面上下文(必须在登录房间前调用)
zg.initContext({
wxContext: this,
pushAtr: 'pusher', // 与 data 中的字段名一致
playAtr: 'playerList', // 与 data 中的字段名一致
});
this.zg = zg;
this.roomId = roomId;
this.userId = userId;
this.token = token;
// 4. 注册事件监听(必须在 loginRoom 前设置)
this.registerEvents();
},
加入房间 + 推拉流
registerEvents() {
const zg = this.zg;
// 房间连接状态
zg.on('roomStateUpdate', (roomID, state, errorCode) => {
if (state === 'CONNECTED') {
// 登录成功后开始推流
this.startPublish();
}
if (state === 'DISCONNECTED') {
this.handleDisconnect(errorCode);
}
});
// 远端流变化(医生进入/离开)
zg.on('roomStreamUpdate', (roomID, updateType, streamList) => {
if (updateType === 'ADD') {
streamList.forEach(stream => this.startPlay(stream.streamID));
}
if (updateType === 'DELETE') {
streamList.forEach(stream => this.stopPlay(stream.streamID));
}
});
},
async joinRoom() {
const { roomId, userId, token } = this;
const result = await this.zg.loginRoom(roomId, token, {
userID: userId,
userName: `用户_${userId}`,
}, {
userUpdate: true,
});
if (!result) {
wx.showToast({ title: '进入房间失败,请重试', icon: 'none' });
}
},
async startPublish() {
const streamID = `${this.userId}_${this.roomId}`;
const zegoPusher = this.selectComponent('#zegoPusher');
await zegoPusher.startPush(this.zg, streamID);
this.localStreamID = streamID;
},
async startPlay(streamID) {
// 更新播放列表,SDK 会自动处理组件渲染
const zegoPlayer = this.selectComponent(`#zegoPlayer_${streamID}`);
if (zegoPlayer) {
await zegoPlayer.startPlay(this.zg, streamID);
}
// 将新流加入列表以触发 wx:for 渲染
const zegoPlayerList = [...this.data.zegoPlayerList, {
id: streamID,
componentID: `zegoPlayer_${streamID}`,
playerId: streamID,
}];
this.setData({ zegoPlayerList });
},
WXML 页面布局示例

麦克风 / 摄像头控制
// 静音切换
toggleMic() {
const { micOn } = this.data;
this.zg.muteMicrophone(!micOn);
this.setData({ micOn: !micOn });
},
// 摄像头开关(仅关闭视频画面,音频保持)
toggleCamera() {
const { cameraOn } = this.data;
this.zg.mutePublishStreamVideo(!cameraOn);
this.setData({ cameraOn: !cameraOn });
},
// 切换前后摄像头(中医望诊场景常用)
switchCamera() {
this.zg.useFrontCamera(!this.data.isFrontCamera);
this.setData({ isFrontCamera: !this.data.isFrontCamera });
},
弱网降级策略
医疗场景对连接稳定性要求高,需要主动处理弱网情况:
// 监听推流网络质量(需在 live-pusher 的 bindnetstatus 中透传)
onPushNetStateChange(e) {
this.zg.updatePlayerNetStatus(this.localStreamID, e);
},
// 监听拉流网络质量
onPlayNetStateChange(e) {
this.zg.updatePlayerNetStatus(e.currentTarget.id, e);
},
// 推流质量回调
setupQualityMonitor() {
this.zg.on('publishQualityUpdate', (streamID, stats) => {
// videoBitrate: 视频码率(Kbps), videoFPS: 帧率
const quality = this.calcQualityLevel(stats.videoBitrate, stats.videoFPS);
this.setData({ networkQuality: quality });
if (quality >= 4) {
wx.showToast({ title: '网络较差,画质已自动降低', icon: 'none' });
}
if (quality === 5) {
wx.showModal({
title: '网络信号极差',
content: '是否切换为纯语音问诊?',
success: (res) => {
if (res.confirm) {
this.zg.mutePublishStreamVideo(true);
this.setData({ cameraOn: false });
}
}
});
}
});
},
结束问诊,释放资源
async endConsultation() {
// 停止推流
const zegoPusher = this.selectComponent('#zegoPusher');
if (zegoPusher) zegoPusher.stopPush();
// 退出房间(SDK 会自动停止所有拉流)
await this.zg.logoutRoom(this.roomId);
// 销毁引擎(重要!防止内存泄漏)
this.zg.destroyEngine();
this.zg = null;
// 跳转到问诊结束页
wx.redirectTo({ url: '/pages/consultation-end/index' });
},
onUnload() {
// 页面卸载时确保资源释放
if (this.zg) {
this.zg.logoutRoom(this.roomId);
this.zg.destroyEngine();
}
},
完整流程图
患者进入问诊页
│
▼
前端请求后端 → 生成 RoomID + Token
│
▼
new ZegoExpressEngine(appID, server)
│
▼
initContext(绑定页面上下文)
│
▼
注册事件监听(roomStateUpdate / roomStreamUpdate)
│
▼
loginRoom(加入房间)
│
├─── roomStateUpdate: CONNECTED
│ └─── zegoPusher.startPush() 开始推本地摄像头
│
├─── roomStreamUpdate: ADD(医生进入)
│ └─── zegoPlayer.startPlay() 拉取医生视频
│
▼
双方视频接通(是)
│
├─── publishQualityUpdate → 弱网降级(降画质 → 关视频 → 纯语音)
├─── 支持静音 / 关摄像头 / 切换摄像头
│
▼
问诊结束 → stopPush → logoutRoom → destroyEngine
│
▼
后端记录问诊记录 + 触发云端录制存档(如需)
合规与审核要点
这部分内容需要注意,这是项目上线被卡的高发区。
互联网医院资质
开展视频问诊必须持有以下资质之一:
- 互联网医院牌照:由省级卫健委审批,依托实体医院设立。
- 卫健委备案证明:部分省份允许第三方平台备案后开展服务。
没有资质直接上线视频问诊,属于违规行为,微信审核也会拒绝。
小程序类目审核
申请”医疗 > 互联网医院”或”医疗 > 在线问诊”类目时,需提交:
- 互联网医院许可证或备案证明
- 医疗机构执业许可证
- 法人身份证明
审核周期通常 3~7 个工作日,建议提前准备。
视频录像的存储合规
根据《互联网诊疗监管细则》,诊疗过程的音视频记录需要:
- 境内服务器存储:不能使用境外云存储。
- 保存期限:不少于 3 年。
- 访问控制:仅授权人员可查看。
ZEGO 提供云端录制服务,支持将录像直接存储到阿里云 OSS、腾讯云 COS 等境内存储,可满足合规要求。
隐私协议中的音视频采集说明
用户协议和隐私政策中必须明确告知:
- 采集摄像头和麦克风数据的目的。
- 数据的存储方式和期限。
- 用户的撤回授权方式。
微信审核会重点检查这一项,缺失会直接导致审核被拒。
处方和病历的电子签名
如果涉及电子处方,需要接入符合《电子签名法》要求的 CA 认证服务,医生的电子签名需具备法律效力。
参考资料
原创文章,作者:ZEGO即构科技,如若转载,请注明出处:https://market-blogs.zego.im/reports-technique/3410/