尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

体育外卖系统怎么做?从业务建模到派单调度的工程实践

体育外卖系统怎么做?从业务建模到派单调度的工程实践 体育外卖系统怎么做从业务建模到派单调度的工程实践「体育外卖」并不是把羽毛球装进餐盒送到家而是把体育服务本身做成可在线下单、就近匹配、限时履约的商品形态。典型场景有三类一是教练上门私教、陪练、球类教学二是场地与场馆的即时预约含无人场馆扫码开台三是器材与装备的即时配送。它和普通电商的区别在于交易对象是「人的时间 场地的时段」履约过程有强时间窗口、强地理约束和人力排班冲突因此系统设计要围绕 LBS 匹配、时段冲突检测、状态机流转这三件事展开。一、先把业务拆成三个域再谈技术选型体育外卖的业务复杂度不在商品详情页而在供给侧的排班与履约。建议在建模阶段就切成三个限界上下文供给域教练/助教/陪练档案、技能标签、可服务时段、可服务半径、接单上限场馆则对应场地、台位、开放时段。交易域下单、计价规则、优惠券与团购核销、支付与退款。履约域派单、抢单、签到、服务中、加钟、完成确认、异常与申诉。技术栈上一套被验证过的组合是后台服务用 Spring Boot MyBatis Plus数据库 MySQL用户端用 UniAppVue 语法一套代码同时编译小程序、H5 和 App管理后台用 Vue Element UI。骑手端/教练端单独做一个轻量 App 或小程序只承载位置上报、接单、导航、签到四件事减少端上复杂度。需要提醒的是体育外卖的多端不是「复制粘贴四份」而是同一套接口服务四种渲染层。如果一开始就为每个端写独立后端后期排班规则一变四个端都要改维护成本会失控。二、派单调度地理围栏 排班过滤的两段式匹配派单是体育外卖的技术核心。常见的错误做法是「查距离近的教练直接推」这会立刻撞上两个问题该教练这个时段已被占用该教练不具备这项技能。正确的做法是两段式先用地理位置粗筛再用排班与技能精筛。位置数据用 Redis GEO 存取写入时以教练/骑手上报的实时坐标为准// 教练端上报位置后写入 GEOredisTemplate.opsForGeo().add(sport:coach:geo,newPoint(longitude,latitude),String.valueOf(coachId));粗筛阶段按服务半径取出候选集合并带上距离排序GeoResultsRedisGeoCommands.GeoLocationStringcandidatesredisTemplate.opsForGeo().radius(sport:coach:geo,newCircle(newPoint(lng,lat),newDistance(serviceRadiusKm,Metrics.KILOMETERS)),RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().sortAscending().limit(50));精筛阶段回到 MySQL用「时段不重叠」这个条件过滤掉冲突的教练。判断逻辑是标准的区间相交已有时段的开始早于新单结束且结束晚于新单开始即为冲突。SELECTc.id,c.name,c.service_radiusFROMcoach cWHEREc.status1ANDc.skill_tagsLIKECONCAT(%,#{skillTag}, %)ANDc.daily_order_countc.daily_limitANDNOTEXISTS(SELECT1FROMcoach_schedule sWHEREs.coach_idc.idANDs.start_time#{endTime}ANDs.end_time#{startTime});这里有个容易被忽略的细节coach_schedule不要只存「已接单」的时段还应把教练自己设置的「不可服务时段」休息、通勤缓冲一起写入。通勤缓冲尤其重要——两单之间只留 5 分钟现实中必然迟到终变成客诉。派单策略上建议同时支持「指派」和「抢单」两种模式指派用于服务标准化程度高的场景如无人场馆开台无需人工抢单用于教练供给弹性大的场景。抢单要注意幂等同一条订单被多人同时抢用数据库乐观锁或 Redis 分布式锁保证只有一人成功// 抢单幂等CAS 更新订单持有者intupdatedorderMapper.acceptIfIdle(orderId,coachId);if(updated0){thrownewBizException(该订单已被其他教练接单);}三、订单状态机与超时兜底体育外卖的订单状态比外卖餐品复杂因为多了「加钟」和「上门/到店」两条分支。建议显式定义一个状态枚举禁止在业务代码里散落if (status 3)这类魔法值。publicenumSportOrderStatus{CREATED,// 已下单待支付或已支付DISPATCHING,// 派单中ACCEPTED,// 教练已接单ON_THE_WAY,// 前往中ARRIVED,// 已到达待签到SERVING,// 服务中PENDING_CONFIRM,// 待用户确认完成FINISHED,// 已完成CANCELED,// 已取消REFUNDING// 退款中}状态流转建议用一张order_status_log表记录每次变更的操作方用户、教练、系统、后台出问题时能直接回溯是谁在什么时间改的。超时兜底是体验的分水岭。派单发出后 N 分钟无人接单订单应自动回流到公共池并扩大服务半径重新匹配教练接单后长时间未开始前往触发提醒。实现上不要用定时任务全表扫描用延迟消息或 Redis ZSet 做延迟队列// 下单成功时投递一条延迟消息到期检查是否仍处于 DISPATCHINGdelayQueue.offer(orderId,Duration.ofMinutes(dispatchTimeout));延迟消息到期后回查订单当前状态如果已流转则直接忽略天然保证幂等。四、多端一致性与券码核销UniApp 的价值在于一套代码多端发布但推送通道是绕不开的差异点小程序走模板消息/订阅消息App 走厂商推送H5 走轮询或 WebSocket。工程上应把「通知」抽象成一个服务接口各端只是不同的实现publicinterfaceNotifyChannel{voidsend(LonguserId,StringtemplateCode,MapString,Objectparams);}订单状态变更时只调用统一入口由路由层根据用户当前活跃端选择通道。另一个高频需求是团购券核销美团、抖音等平台的券码。核销接口必须做幂等同一券码重复请求只生效一次用索引或SELECT ... FOR UPDATE加状态判断都可以。核销成功后要异步回调第三方平台并记录回调结果失败则进入重试队列——这一步漏了会出现「用户这边显示已用平台那边还是待核销」的对账问题。无人场馆类的体育外卖还会涉及开台控制门禁、灯光、计时计费。建议把设备控制做成独立的下游服务通过 MQTT 或厂商 SDK 下发指令主交易链路只发一条事件消息不直接同步调用硬件避免设备离线拖垮下单接口。FAQQ1体育外卖和普通外卖系统的技术差异主要在哪主要是供给侧模型不同。普通外卖的商品是库存体育外卖的供给是「人的可服务时段 地理位置」必须做时段冲突检测和通勤缓冲否则会出现接单后无法履约。Q2派单用 Redis GEO 够用吗数据量大会不会有性能问题单城市几万量级的教练/骑手坐标Redis GEO 完全够用。要注意的是 GEO 只做粗筛终候选必须回库做排班与技能过滤不要把业务规则塞进 Redis。Q3订单状态机用枚举够吗还是必须上状态机框架状态数量在 10 个以内、流转规则相对固定时枚举 集中式流转校验方法就够。只有当不同业务线上门、到店、无人场馆流转规则差异很大时才值得引入状态机框架。Q4多端发布时容易被低估的工作是什么不是 UI 适配而是推送与登录态。小程序、App、H5 的登录凭证有效期和推送通道都不同建议在架构设计阶段就把这两块抽象成独立服务。Q5券码核销的幂等怎么做稳给券码建索引核销时先插入核销记录、成功后再更新订单状态重复请求会因键冲突直接失败并返回已核销结果比先查后写更可靠。
返回列表