医院陪诊这块业务这两年肉眼可见地火了起来,尤其是一二线城市,独居老人就医、异地就诊、孕妇产检、术后复查这些场景,需求非常刚性。我之前帮一家本地生活服务公司从零搭过一整套医疗陪诊系统,涵盖微信小程序用户端、陪诊师端APP和后台管理面板,这里把整个开发过程里踩过的坑、沉淀下来的功能模块设计思路,以及源码层面的关键实现细节整理成文。如果你正准备做医院陪诊APP或小程序,这篇文章应该能帮你少走不少弯路。
整篇内容会按照我实际开发的顺序来走:先拆清楚业务逻辑和产品形态,再逐层分析用户端、陪诊师端、后台管理端的功能模块,最后落到技术实现细节和常见的线上问题。每个模块我都会给出可落地的设计方案,关键代码部分标注清楚实现思路,方便你直接抄作业或者二次开发。
1. 医疗陪诊系统开发的核心思路拆解
1.1 陪诊服务到底在解决什么痛点
做系统之前,先得想明白陪诊服务的本质。它不是什么高端医疗服务,本质上就是“时间换时间、专业换省心”的跑腿服务升级版,只不过跑的是医院内部流程,需要的是对医院环境、就诊流程的熟悉程度和一定的健康照护常识。
典型的陪诊场景分为四类:
- 老人就医陪同:子女在外地工作,老人自己去医院搞不定挂号、取号、科室楼层、缴费、取药这一串流程,需要有人全程跟着。
- 异地就医向导:患者从外地来大医院看病,不熟悉院区分布,往往连门诊楼和医技楼都分不清,陪诊师能显著减少无效奔波。
- 特定人群照护:孕妇产检、术后复查、行动不便人群,需要有人帮忙推轮椅、拿报告、听医嘱。
- 业务代办跑腿:代取报告、代开药、代缴费、代办出入院手续,这类业务客单价低但频率高,适合作为引流产品。
从系统设计的角度来看,这四个场景的核心链路是相同的:患者发起需求 → 平台匹配陪诊师 → 按约定时间到场衔接 → 陪诊过程中进行服务记录 → 结束结算。围绕这条主链路去设计功能模块,业务逻辑才不会乱。
1.2 平台模式与产品形态的选型逻辑
产品形态到底是做APP还是做小程序,这是动工前必须定下来的事。我的建议是:用户端必须优先做微信小程序,陪诊师端可以上APP,也可以做小程序,但要做APP功能更顺手。
为什么用户端选小程序?理由很实际:
- 用户不需要下载安装,微信里搜一下或者扫码就能用,这个对中老年用户群体尤其友好,他们的手机存储空间和操作习惯都偏向轻量应用。
- 小程序天然具备微信支付的闭环能力,用户支付和退款流程都是现成的,不需要额外申请支付渠道。
- 分享传播链路短,子女帮父母下单后直接转发小程序卡片给老人,老人点开就能看到订单信息,体验非常顺。
陪诊师端之所以推荐APP形态,是因为陪诊师在外面跑单时,需要频繁切换网络、调用摄像头拍照上传、实时上报位置,还要接收订单推送。小程序有不少场景限制,比如长时间后台运行时的定位频率会被压低,容易被系统回收。做原生APP或者uniapp打包的APP,在权限控制和后台保活方面会从容很多。
后台管理端没有悬念,直接做Web管理后台,PC端操作。这里有个容易被忽略的点:管理后台不只是给运营人员用的,还要考虑客服坐席的接入,订单纠纷处理、陪诊师资质审核、保险理赔记录这些功能都要在后台找到对应的入口。
2. 功能模块全解析:前端用户端的核心设计
2.1 在线预约与陪诊师匹配模块
用户端的预约流程看上去简单,但实施起来比想象中复杂。本质上是一个“需求描述 → 系统拆解 → 人货匹配 → 确认支付”的过程,每一步都要有对应的功能支撑。
预约表单的核心字段我建议这样设计:
| 字段名 | 是否必填 | 说明 |
|---|---|---|
| 就诊城市 | 必填 | 默认定位城市,可手动切换 |
| 医院名称 | 必填 | 提供医院库搜索,支持首字母检索 |
| 就诊科室 | 必填 | 该医院科室列表,数据要维护准确 |
| 陪诊日期 | 必填 | 日期选择器,限制只能选未来7天 |
| 开始时间 | 必填 | 上午/下午/具体时段 |
| 患者姓名 | 必填 | 支持填写多个就诊人 |
| 患者手机号 | 必填 | 下单通知用 |
| 病情简述 | 选填 | 用于陪诊师提前了解情况 |
| 服务类型 | 必填 | 全程陪诊/半程陪诊/代取报告/代办跑腿 |
| 特殊需求 | 选填 | 轮椅、担架、翻译、儿童看护等 |
这里最关键的是服务类型的划分,我强烈建议在预约之前先让用户选择服务类型,然后把表单动态渲染成对应的字段组合。比如用户选“代取报告”,就根本不需要展示陪诊日期和开始时间,只需要让用户上传就诊卡照片或填写就诊卡号,再选一个期望取报告的时间窗口。表单字段跟着服务类型动态变化,用户下单的路径会短很多,转化率明显不一样。
陪诊师匹配这块不用做得太重,不建议一开始就搞复杂的推荐算法。线上跑下来的经验是,用行政区 + 服务类型 + 当日空闲时段这三个维度做筛选条件,拉出候选陪诊师列表,按接单量排序,用户可以直接看到陪诊师的姓名、照片、服务年限、接单次数和用户评价,自己选。对于完全不想选的用户,提供一个“平台智能推荐”按钮,系统自动选排名第一的陪诊师,也说得过去。
2.2 订单状态机与全流程追踪模块
订单状态设计是整个系统最核心的部分,状态定义不清楚,后面所有环节都会跟着乱套。我们线上实际跑的订单状态机是这样的:
- 待支付:用户提交预约后未付款,超过15分钟自动取消。
- 待接单:付款成功,等待陪诊师接单。
- 已接单:陪诊师接单,进入服务准备阶段。
- 服务中:陪诊师已到达医院打卡,或用户确认开始服务。
- 待完成:服务已经结束,等待用户确认。
- 已完成:用户确认完成,订单关闭,进入评价和结算环节。
- 已取消:用户或陪诊师主动取消,或系统超时取消。
- 退款中/已退款:取消后触发退款流程,资金原路退回。
状态机的每一次跳转,都要在两个端同步推送通知。比如“待接单”到“已接单”,用户端要推模板消息“您的陪诊师已接单”,陪诊师端要推APP推送“您有新订单,请及时处理”。
订单状态变更的记录不要只存在数据库表里,建议单独拉一张订单状态流转日志表,按时间顺序记录状态变更前后的值、操作人ID、变更原因和来源端。这张表是后面做纠纷取证和数据分析的底子,别省。
订单地图追踪模块做起来要注意一个细节:陪诊师的位置更新频率不能一刀切。如果每3秒上报一次经纬度,服务器压力会很大,而且电池消耗也快。我们的方案是:服务开始前10分钟高频上报(每5秒一次),服务中每10秒一次,在分诊台、检验科这类重点区域内切到高频模式。判断“重点区域”的逻辑是后台可以手工配置的坐标围栏,陪诊师进入围栏后客户端自动提高定位频率。
2.3 在线支付与费用核算模块
支付模块强调的是准确性和可追溯性。用户端接入微信支付已经很成熟了,关键在业务层面的费用计算逻辑。
费用结构我建议分成三段:
- 基础服务费:按照服务类型和时长计算。比如全程陪诊(4小时)定价268元,超时每小时加收50元。
- 附加服务费:特殊需求产生的费用,比如轮椅租赁、夜间服务、跨院区陪同,在订单确认前明确展示给用户。
- 履约保证金:部分服务场景需要交一笔小额保证金(比如代取贵重报告、代结算费用),服务完成后原路退还。
线上运营中要注意一个问题:陪诊师在医院代缴的费用(挂号费、检查费、药费)和平台服务费必须分开结算。如果混在一起,对账的时候会非常痛苦。我们当时的处理方式是:服平台只收取服务费,用户在订单支付时只付服务相关费用;陪诊师代垫的医院费用,在服务完成后用户单独通过平台的“代付账单”入口支付,这笔钱不走平台账户,直接经过微信支付分账到陪诊师的收款账户。
好处有两个:平台现金流风险小,如果用户拖欠医院费用,纠纷主体在用户和陪诊师之间,平台只做监管;陪诊师垫付压力小,合作意愿更高,服务态度也更稳定。
3. 陪诊师端与后台管理端的功能架构
3.1 陪诊师接单与服务记录模块
陪诊师端的第一屏应该是“接单大厅”,所有待接订单按距离和时间排序展示。接单逻辑上,为了避免老手抢单导致新陪诊师接不到活,可以做成轮询派单队列的模式:系统按照“区域匹配优先,接单率高的排前,当前空闲的优先”规则生成候选队列,依次推送。被拒单后自动流转给队列下一位,陪诊师没有主动抢单的操作,只有“接受”和“放弃”两个选择。
这里有个坑:新开城的时候陪诊师数量少,每次派单都推给同一个人,时间长了陪诊师会觉得平台在压榨自己,接单意愿会大幅下降。后来我们加了一条规则,同一位陪诊师一天最多接6单,距离超过5公里且用户未选择“优先离我最近”的订单,不再推送给这位陪诊师。接单体验明显平稳了。
服务记录模块承载着证据链的作用。陪诊师到达医院后需要在APP上打卡,打卡方式支持GPS定位打卡和扫码打卡两种。GPS打卡要求陪诊师在医院坐标围栏范围内,扫码打卡则是扫医院大厅的固定二维码,扫码时要附带定位信息一起上传,防止造假。
服务过程中要引导陪诊师拍摄关键节点照片:取号成功、排队签到、医生问诊、缴费、取药、报告出具,每个节点拍一张,水印时间地点自动打上。这些照片一方面作为服务完成的证据,另一方面是后续用户投诉时的仲裁依据,有图有真相能省掉大量扯皮成本。
3.2 后台运营管理与风控模块
后台管理端模块划分可以参考下面的结构:
- 订单管理中心:按订单状态筛选,支持按陪诊师/用户/手机号/订单号全局搜索。
- 陪诊师管理:准入审核、证照上传、服务区域设置、接单能力配置、奖惩记录。
- 用户管理:用户列表、就诊人管理、投诉记录、信用分管理。
- 财务管理:订单流水、陪诊师结算单、退款记录、发票管理。
- 医院信息库:医院列表、科室数据、院区坐标围栏、服务范围配置。
- 消息运营中心:模板消息审核、用户推送运营、优惠券配置。
- 数据看板:实时订单量、完单率、退单率、平均响应时长、GMV趋势。
- 系统配置:套餐价格、服务类型、审核规则、权限角色。
风控模块是很多自研团队容易忽略的部分。陪诊业务最大的风险不是技术风险,而是线下服务人员的不确定性。我把风控拆成了三块:
准入风控:陪诊师入驻必须实名认证+健康证上传+无犯罪记录声明,还需要上传一张本人近期照片。资质材料要过人工审核,绝不能全自动通过,全自动在线上跑过几轮就会发现有人拿PS过的证件蒙混过关。
过程风控:平台抽查陪诊师的实时轨迹,连续两次抽查发现陪诊师不在医院围栏范围内且未提前报备的,自动触发警告消息。跑单过程中用户取消订单或修改时间超过两次的,订单会进入后台人工关注列表。
售后风控:用户评价里出现“迟到”“不专业”“乱收费”关键词的,自动推送给客服回访。陪诊师被投诉两次以上且成立,直接暂停接单,重新培训考核通过后才能上线。
这里特别说明一下,做陪诊系统不要一上来就堆砌太重的AI风控能力,人脸识别、活体检测这些东西成本不低,初期用人工审核+规则引擎完全够用,等订单量稳定了再逐步加自动化能力。
4. 技术实现细节:小程序与APP的源码实践
4.1 微信小程序的登录与手机号授权流程
微信小程序登录这块看起来文档很清楚,实操起来细节很多。一定记住当前的推荐姿势是:wx.login拿到code,再用code换openid和session_key,整个过程在后端完成,不要把session_key下发到前端。
先看基础登录代码:
// 前端:小程序端逻辑 Page({ data: { isLogined: false }, onLoad() { this.checkLoginStatus(); }, checkLoginStatus() { const token = wx.getStorageSync('token'); if (token) { this.setData({ isLogined: true }); return; } this.login(); }, login() { wx.login({ success: (res) => { if (res.code) { // 把code传到后端,后端用code去微信接口换session wx.request({ url: 'https://api.example.com/user/login', method: 'POST', data: { code: res.code }, success: (resp) => { if (resp.data.code === 0) { wx.setStorageSync('token', resp.data.data.token); this.setData({ isLogined: true }); } } }); } } }); } });后端处理的核心代码逻辑,用Node.js示例:
// 后端:Node.js + Express const crypto = require('crypto'); const axios = require('axios'); const APPID = 'your-appid'; const SECRET = 'your-app-secret'; async function wxLogin(code) { const url = `https://api.weixin.qq.com/sns/jscode2session?appid=${APPID}&secret=${SECRET}&js_code=${code}&grant_type=authorization_code`; const response = await axios.get(url); const { openid, session_key, errcode } = response.data; if (errcode) { throw new Error(`微信登录失败: ${errcode}`); } // openid对应查数据库,新用户自动注册 let user = await UserModel.findOne({ openid }); if (!user) { user = await UserModel.create({ openid, avatar: '', nickname: `用户${openid.slice(-6)}` }); } // 用session_key配合业务逻辑生成自己的登录态token const token = crypto.randomBytes(32).toString('hex'); user.token = token; user.sessionKey = session_key; await user.save(); return { token, openid }; }手机号授权方面,目前微信政策调整后,必须使用button组件的open-type="getPhoneNumber"来触发授权,不能直接调API,而且要确保小程序后台已经申请了对应权限。拿到code之后后端用code换取手机号,配合session_key做解密也行,新接口是直接用code换手机号,省掉了手动解密的过程。
下面是一个带手机号绑定的完整实现:
// 前端:获取手机号的button <button class="phone-btn" open-type="getPhoneNumber" bindgetphonenumber="onGetPhoneNumber"> 微信一键登录 </button>// 前端:处理手机号授权回调 Page({ onGetPhoneNumber(e) { const { code, errMsg } = e.detail; if (!code) { wx.showToast({ title: '您取消了授权', icon: 'none' }); return; } wx.login({ success: (res) => { wx.request({ url: 'https://api.example.com/user/bindPhone', method: 'POST', data: { loginCode: res.code, phoneCode: code }, success: (resp) => { if (resp.data.code === 0) { wx.setStorageSync('token', resp.data.data.token); wx.showToast({ title: '登录成功' }); } } }); } }); } });后端处理手机号code的代码:
async function bindPhone(loginCode, phoneCode) { const sessionRes = await axios.get( `https://api.weixin.qq.com/sns/jscode2session?appid=${APPID}&secret=${SECRET}&js_code=${loginCode}&grant_type=authorization_code` ); const { openid } = sessionRes.data; // 用phoneCode去微信接口换手机号 const phoneRes = await axios.post( `https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=${getAccessToken()}`, { code: phoneCode } ); if (phoneRes.data.errcode === 0) { const phoneNumber = phoneRes.data.phone_info.phoneNumber; // 更新用户绑定的手机号 await UserModel.updateOne({ openid }, { phone: phoneNumber }); // 生成登录态token并返回 const token = crypto.randomBytes(32).toString('hex'); await UserModel.updateOne({ openid }, { token }); return { token }; } throw new Error('获取手机号失败'); }一个重要的提醒:不要在小程序前端直接解析手机号数据。新接口返回的手机号是加密的,应把code原样传到后端处理。前几年有部分开发者在前端用session_key解密,新规则之后这条路已经完全堵死了。
4.2 订单状态机的源码实现思路
订单状态机的核心是一个有限状态模型,把每个状态允许的转移动作和第二校验条件内置到代码里面。用Java或者Node.js实现都可以,我摘一段我们实际使用的Node.js版本的简化代码:
const ORDER_STATUS = { PENDING_PAY: 'pending_pay', // 待支付 PENDING_ACCEPT: 'pending_accept', // 待接单 ACCEPTED: 'accepted', // 已接单 IN_SERVICE: 'in_service', // 服务中 PENDING_COMPLETE: 'pending_complete', // 待完成 COMPLETED: 'completed', // 已完成 CANCELLED: 'cancelled', // 已取消 REFUNDING: 'refunding', // 退款中 REFUNDED: 'refunded' // 已退款 }; const TRANSITION_RULES = { [ORDER_STATUS.PENDING_PAY]: { to: [ORDER_STATUS.PENDING_ACCEPT, ORDER_STATUS.CANCELLED], validate: (ctx) => { // 待支付 -> 待接单:必须是支付成功回调触发 if (ctx.action !== 'pay_success' && ctx.action !== 'pay_callback') { throw new Error('非法的状态迁移'); } } }, [ORDER_STATUS.PENDING_ACCEPT]: { to: [ORDER_STATUS.ACCEPTED, ORDER_STATUS.CANCELLED], validate: (ctx) => { // 待接单 -> 已接单:必须是指定陪诊师接单 if (ctx.action !== 'accept_order') { throw new Error('非法操作'); } } }, [ORDER_STATUS.ACCEPTED]: { to: [ORDER_STATUS.IN_SERVICE, ORDER_STATUS.CANCELLED], validate: (ctx) => { if (ctx.action === 'start_service') { const now = Date.now(); const serviceStartTime = ctx.order.serviceTime; // 陪诊师只能在约定时间前后30分钟内开始服务 if (Math.abs(now - serviceStartTime) > 30 * 60 * 1000) { throw new Error('不在允许的服务开始时间窗口内'); } } } }, // ... 其他状态转移规则 }; function transitionOrder(order, targetStatus, ctx) { const rule = TRANSITION_RULES[order.status]; if (!rule || !rule.to.includes(targetStatus)) { return { success: false, message: `状态${order.status}不可迁移到${targetStatus}` }; } try { rule.validate(ctx); } catch (err) { return { success: false, message: err.message }; } order.status = targetStatus; order.transitionLogs.push({ from: order.status, to: targetStatus, operator: ctx.operatorId, action: ctx.action, timestamp: Date.now() }); return { success: true, order }; }把状态转移规则集中在一个文件里,好处是所有业务流程都能复用同一套校验逻辑。哪天想加一个“超时自动取消”的状态,只需要在PENDING_PAY里加一条到CANCELLED的转移规则,并写一个定时任务去触发,不需要改动业务层代码。
订单模块还有两个容易出现并发问题的场景:用户端同时取消订单和陪诊师端同时接单。如果直接在数据库层面UPDATE,很容易出现双方都认为操作成功的情况。解决方案有两个方向:一是订单表中加版本号version字段,使用乐观锁,UPDATE的条件带上version;二是把接单和取消的操作封装到单机的Redis锁里面,同一订单ID只能串行处理。我们生产环境用的是第二种,用Redis的SETNX实现分布式锁:
const Redis = require('ioredis'); const redis = new Redis(); async function withOrderLock(orderId, callback, ttl = 5) { const lockKey = `order:lock:${orderId}`; const token = `${Date.now()}_${Math.random()}`; const result = await redis.set(lockKey, token, 'EX', ttl, 'NX'); if (!result) { throw new Error('订单正在处理中,请勿重复操作'); } try { await callback(); } finally { const currentToken = await redis.get(lockKey); if (currentToken === token) { await redis.del(lockKey); } } } // 使用方式 await withOrderLock(orderId, async () => { await cancelOrder(orderId, operatorId); });提醒一点,锁的过期时间设5秒还是10秒,取决于你订单回调里要执行多少次数据库操作。太短容易出现锁提前失效,太长则回调挂了锁要等很久才自动释放。压测环境下把每个关键链路的时间测一遍,再给个50%余量,就差不多合适了。
4.3 小程序定位、导航与消息推送的接入要点
定位模块是陪诊系统里隐形成本最高的部分。腾讯地图、高德地图、微信原生getLocation,选择策略要提前想好。
微信小程序的getLocation接口目前要求在app.json里声明requiredPrivateInfos权限字段,还要在后台开通调用权限,否则会报错。我们线上配置是这样的:
// app.json { "requiredPrivateInfos": [ "getLocation", "chooseLocation" ], "permission": { "scope.userLocation": { "desc": "你的位置信息将用于匹配附近的陪诊师" } } }前端获取定位后,要把经纬度传给后端做逆地理编码。这里注意一个点:不要在前端直接调逆地理编码接口,因为小程序端填reverseGeocoder接口的key如果被暴露,一天的免费额度可能被打爆。正确做法是前端只把latitude和longitude传给后端,后端用自己的密钥去调逆地理编码服务。
导航功能不要自己做路径规划,直接调腾讯地图小程序插件或者高德地图的URL API。用户点“去这里”按钮,调起外部导航,既保证路线准确性,又省掉了大量算路引擎的开发量。
消息推送这里有个容易踩的坑:微信小程序的订阅消息是一次性订阅,用户每次授权只能接收一次模板消息,但陪诊订单的整个生命周期不止一次消息提醒。我们处理方案是:在下单成功、陪诊师接单、服务开始、服务完成这些关键节点前,先弹窗向用户申请一次性订阅授权。用户如果不点授权,这些消息就发送失败。业务上为了让订阅率更高,我们做了一个小设计:下单支付完成后立即弹订阅授权,文案写清楚“授权后您可以及时收到陪诊师接单和订单进度通知”,实测订阅率能到60%左右。
对于陪诊师APP端的推送,直接对接极光推送或者个推,不推荐自己做长连接。Android端要注意厂商通道的接入,华为、小米、OPPO、vivo各自有独立的厂商推送SDK,如果不接厂商通道,App在系统省电策略下推送会有明显延迟。
4.4 小程序抓包的调试经验
项目开发过程中,排查线上问题最常用的手段就是抓包。小程序抓包可以通过手机代理到Charles或Fiddler,也可以直接使用开发者工具的“本地调试”模式。本地调试模式下,小程序的request会自动指向开发环境域名,配合编辑器里的调试器和Network面板,看请求和响应都是全透明的,日常调试效率比手机抓包高得多。
如果你需要查看小程序生产环境的真实请求数据,推荐用Charles做代理抓HTTPS包,但需要先安装Charles根证书,并且小程序开发工具里要信任该证书。实际操作中我遇到过两种典型问题:一是微信小程序的https请求强制要求TLS 1.2以上,Charles低版本生成的证书可能不满足,连不上;二是主包请求带上了public key pinning校验,抓包会直接看到SSLHandshake错误。遇到这种,最稳妥的排查方式还是去服务器侧看nginx日志,用access_log里的请求参数反向定位。
小程序在调试模式下会开启“不校验合法域名”,这个功能便于联调,但上线前一定要关掉。我们团队就有同事带着这个开关把build产物发到生产环境,结果正式版本所有请求直接被判非法域名,整整一个下午线上服务不可用。
5. 开发过程中的常见问题与排查实录
5.1 小程序请求超时与token过期
问题现象:用户反馈订单列表打开非常慢,甚至直接白屏;操作一段时间后所有接口开始报401。
排查过程:我们线上一开始用默认的wx.request,没有单独设置超时时间,微信小程序默认超时是60秒,实际感觉就是转圈半天然后失败。后来把所有请求超时时间统一调整到10秒,服务端网关层加上了性能监控,明确看到个别拉取陪诊师列表的接口在高峰期要耗时2秒以上。排查发现是数据库查询里没有对city和available_status建联合索引,加上之后耗时降到200毫秒以内。
token过期的问题更隐蔽。我们的token有效期设置的是24小时,但小程序的session_key会在用户长时间不用后失效,导致手机号重新授权时各种乱。最后方案是:token的有效期缩短到12小时,但每次请求时在Redis里续期,连续7天活跃的token自动续期到14天;一旦返回401,前端直接跳登录页,让用户重新通过wx.login拿新code刷新token。
5.2 微信小程序解手机号时的步骤误区
Integrating new getPhoneNumber接口时,一个很容易出错的点就是前端bindgetphonenumber拿到code之后,后端用code换手机号需要服务端access_token。如果你在服务端没有维护access_token的缓存,每次都重新调用https://api.weixin.qq.com/cgi-bin/token去申请,接口调用频率很容易超限。微信的access_token有效期是7200秒,官方建议是服务端缓存access_token,全局只维护一个,多实例部署的话还要考虑分布式锁避免同一个token申请多次。
另一个问题是手机号授权code的有效期只有5分钟,用完之后立即作废。如果用户授权后网络不稳定导致请求失败,再次授权要重新弹窗。前端的兜底逻辑要写好,调用接口失败时提示“请重新点击授权”,而不是直接卡死在页面上。
5.3 陪诊师端定位偏移与轨迹漂移
问题现象:后台看到的陪诊师轨迹穿过河流、隔空跨越大楼,明明在医院里定位却显示几百米外。
原因分析:定位漂移大多来自室内场景。医院大楼钢筋密集,GPS信号折射后经纬度偏移非常正常,尤其是地下停车场和大型医技楼。第二个原因是部分Android机型的高精度定位设置没有打开,默认用的基站定位和WiFi定位上报,精度在100米到500米之间,完全没法用。
解决方案:客户端统一使用微信小程序的wx.startLocationUpdateBackground或原生定位SDK的高精度模式,内部自动把GPS、WiFi、基站数据进行融合。后端处理轨迹时加了一个简单的卡尔曼滤波算法,把漂移点过滤掉。另外额外加了一条兜底规则:如果陪诊师连续3个点位的平均速度超过6米/秒(相当于人类跑步速度),判定为漂移数据,轨迹不展示给用户,只记录原始数据供人工复核。
// 后端简单的卡尔曼滤波示例(简化版) function kalmanFilter(points) { const filtered = []; const Q = 0.01; // 过程噪声 const R = 5; // 测量噪声 let x = points[0].lat; let y = points[0].lng; let p = 1; filtered.push(points[0]); for (let i = 1; i < points.length; i++) { const z = { lat: points[i].lat, lng: points[i].lng }; // 预测 x = x; y = y; p = p + Q; // 更新 const k = p / (p + R); x = x + k * (z.lat - x); y = y + k * (z.lng - y); p = (1 - k) * p; filtered.push({ lat: x, lng: y }); } return filtered; }这个滤波器说实话不算高级,但对解决视觉上的轨迹跳变已经足够了。更高精度的方案要上HMM平滑,工程复杂度高不少,业务上完全没有必要。
5.4 小程序端连接不上服务器的排查清单
线上反馈“小程序打不开”是比较常见的工单类型,我整理了一份排查清单,按顺序执行可以快速定位问题:
- 确认小程序官方后台是否把request合法域名配置完整,域名是否带https前缀,证书是否过期。
- 确认服务器安全组/防火墙有没有放行小程序服务的端口,现在大部分问题反而是服务器端口被云平台安全策略拦掉。
- 在服务器上直接
curl -I https://api.example.com看响应头,network层面的问题一眼就能看出来。 - 查看nginx错误日志中是否有SSL握手错误,确认证书链是否完整。
- 检查反向代理到Java/Node服务的upstream服务是不是挂了,服务进程是不是OOM了。
小程序端的错误码也需要熟悉几个常见的:-1为网络繁忙或无网络,1005为域名未备案或证书无效,1006为域名离线,1015为访问被限制。遇到1005优先去查域名备案状态,遇到1015优先看服务器防火墙有没有把微信服务器IP段封了,别在业务代码里瞎debug半天。
6. 源码交付与二次开发注意事项
6.1 项目目录结构与核心依赖
一个标准的陪诊系统源码工程,建议拆成三个子工程:
weixin-client:微信小程序用户端,原生小程序技术栈。accompany-app:陪诊师端,uniapp打包成Android/iOS,一套代码两套壳。admin-dashboard:后台管理端,Vue3 + Element Plus。server:后端服务,Node.js或者Java都可以,按团队熟悉度选择。
后端工程内部的模块划分建议:
server/ ├── src/ │ ├── modules/ │ │ ├── user/ # 用户模块 │ │ ├── order/ # 订单模块 │ │ ├── companion/ # 陪诊师模块 │ │ ├── pay/ # 支付模块 │ │ ├── message/ # 消息推送模块 │ │ ├── hospital/ # 医院数据模块 │ │ └── audit/ # 风控审核模块 │ ├── common/ # 公共组件、工具类 │ ├── config/ # 全局配置 │ └── app.js模块划分的核心原则是依赖方向要清晰,不能循环依赖。比如订单模块可以依赖用户模块拿到用户信息,但用户模块绝不能反过来依赖订单模块,否则排查问题的时候你会陷入循环引用的泥潭。
6.2 数据库表设计要点
陪诊业务的表结构其实不复杂,核心表就8张左右:
user:用户表,openid、nickname、phone、avatar、credit_score。patient:就诊人表,关联user_id,姓名、身份证、关系、病历号。companion:陪诊师表,实名信息、资质材料、服务区域、评分、累计单量。order:订单主表,订单号、用户ID、陪诊师ID、状态、服务时间、费用明细。order_status_log:订单状态流转日志表。service_record:服务记录表,打卡信息、节点照片、服务备注。pay_record:支付流水表,微信支付订单号、金额、状态、回调信息。withdraw_record:陪诊师提现记录表。
重点说一下订单表里容易忽略的几个字段:
service_address:服务医院的地址快照,下单时从医院库读取存下来,防止以后医院库更新导致旧订单查不到医院。fee_detail:费用明细JSON字段,存基础服务费、附加费、保证金的拆分记录。source_type:订单来源,区分小程序自助下单、客服代下单、渠道推广订单。first_paid_at:首付款时间,做支付转化率分析时用。accept_timeout_at:接单超时时间,超时后系统自动重新派单。
费用相关字段不要用浮点类型,项目重点记录使用的是整型的“分”为单位的金额,避免出现0.1+0.2不等于0.3这类精度问题。
6.3 二次开发时最容易改坏的三处地方
改接口鉴权。很多二次开发的团队上来先把登录逻辑换成自己的鉴权方案,结果小程序端的token刷新生效策略没对齐,导致用户频繁重新登录。改鉴权一定要先梳理token的生成、校验、续期、失效四个环节,缺少任何一个环节都会埋坑。
改状态机。给订单加新状态或者新流转路径时,只改了前端页面的按钮显隐,没改后端的状态转移规则,结果前端能点按钮但后端永远返回“非法状态迁移”。正确的做法是先改状态枚举和TRANSITION_RULES,再改前端页面,两端必须同步上线。
改费用计算。动到费用计算逻辑的时候,一定要检查是否有历史订单正在服务中或已完成但未结算。如果改了计价规则,老订单是按旧规则还是新规则结算?我们的经验是永远做“规则版本化”,在订单表里存储price_version字段,结算时读到哪个版本就用哪个版本计算,谁也别覆盖谁。
7. 上线后的运营经验与个人心得
系统开发完只是第一步,医生陪诊这个业务能不能跑通,关键还是要看线下的服务质量和平台的运营策略。几个经验分享给大家。
第一个心得是千万别在冷启动阶段撒网太多城市。我们刚开始把目标定在六个城市,结果每个城市的医院库、科室数据、陪诊师招募、当地医院流程熟悉程度全都是瓶颈。后来聚焦在一个城市的两三家三甲医院,把陪诊师数量控制在15人左右,把每一单的服务质量打磨稳定,再逐城复制,反而跑得更快。
第二个心得是陪诊师的排班和调度比想象中重要。系统上线的第一个月,经常出现早上8点到10点订单扎堆但陪诊师只有一两个在线,下午订单寥寥但陪诊师都在闲等。后来让陪诊师在后台自主设置可用时段,平台再按历史数据给出建议出勤时间,匹配成功率提升非常明显。这个看似简单的功能,早期做进系统里能省掉运营人员大量人工协调成本。
第三个心得是服务评价体系要做成双向互评。用户给陪诊师打分的同时,陪诊师也可以标记用户,比如“预约时间多次变动”“态度恶劣”“有传染病未提前告知”。这些标记不对外公开展示,只用于平台做派单策略,能有效保护陪诊师群体的利益。双向互评搞起来之后,双方的规则感都会被强化,纠纷率反而比单向评价时更低。
四年来,从第一版陪诊系统原型到线上稳定运行,我最大的体会是,这个系统在技术层面没有太高深的东西,真正决定项目成败的往往是最基础的状态管理、费用准确性、定位可靠性和消息触达率。把这些基础细节做扎实,配合可靠的服务团队,这个系统就能为用户创造真实价值,平台也才能可持续运转。