一套完整同城外卖平台包含四类终端,很多自研项目在开发时采用多套独立接口,终端之间数据不同步、消息推送割裂。常见问题:订单状态变更后,用户、商家、骑手接收消息不一致;配送调度逻辑耦合在订单代码中,抢单、派单模式切换困难;商家商品、门店配送范围、营销活动硬编码,运营配置成本高;缺少统一鉴权体系,不同角色权限越权风险高;午晚高峰并发下单、骑手高频上报位置,直接压垮数据库;资金结算、对账链路分散,订单、支付、结算流水核对困难。 本文基于可落地源码方案,讲解四端协同架构、分层设计、核心业务流程以及关键模块代码实现,兼顾初创单城部署和后期多城扩容。

整体采用SpringBoot + UniApp + MySQL + Redis + MQ + WebSocket架构,中小体量优先模块化单体,流量增长后平滑拆分为微服务,统一网关作为所有终端入口,实现鉴权、限流、参数校验统一处理。
架构分层:接入网关层 → 角色权限层 → 业务模块层(用户、商家、订单、调度配送、营销、结算) → 数据缓存层。 四端数据流:用户下单请求经过网关 → 订单服务校验 → 生成订单事件投递MQ → 同时推送商家端、进入调度池匹配骑手;骑手接单、取餐、送达事件反向推送用户与商家,所有变更记录落库留痕。

用户端:浏览商家、商品加购、下单支付、查看骑手实时轨迹、发起售后评价;只允许查询自身订单、地址、优惠券,无法访问商家配置、骑手数据、结算信息。 商家端:门店信息维护、商品SKU上下架、接单拒单、出餐状态更新、查看门店订单流水、提现对账;权限隔离到门店ID,商家只能操作自己门店数据。 骑手端:上线/下线、接收订单推送、抢单或接收派单、取餐打卡、上传定位轨迹、送达确认、查看收入明细;骑手只能看到分配给自己的订单。 平台后台:城市管理、门店审核、骑手资质审核、配送围栏配置、营销活动配置、订单监控、财务对账、投诉工单处理、数据报表;按管理员角色分配权限,支持门店管理员、财务、超级管理员分级。
权限体系统一在网关拦截,JWT令牌携带角色标识、所属门店/骑手ID,后端接口不依赖前端隐藏按钮做权限控制。
/**
* 统一权限校验切面示例
*/
@Aspect
@Component
public class AuthAspect {
@Autowired
private TokenService tokenService;
@Before("@annotation(authCheck)")
public void before(JoinPoint point, AuthCheck authCheck) {
HttpServletRequest request = ServletUtil.getRequest();
String token = request.getHeader("token");
LoginUser loginUser = tokenService.parseToken(token);
if (loginUser == null) {
throw new BizException("请先登录");
}
// 校验角色
if (!authCheck.role().contains(loginUser.getRole())) {
throw new BizException("无访问权限");
}
// 存入上下文,业务层用于数据隔离
UserContextHolder.set(loginUser);
}
}订单是四端联动核心,采用状态机管控流转,防止非法状态跳转。标准流转链路:待支付 → 商家待接单 → 制作中 → 待取餐 → 配送中 → 已送达 → 已完成;同时支持取消、退款、拒单分支。 下单流程串行校验配送范围、商品库存、起送价、优惠规则;支付回调做幂等处理,支付成功投递MQ触发商家通知、调度派单。
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private OrderItemMapper orderItemMapper;
@Autowired
private RocketMQTemplate rocketMQTemplate;
@Transactional(rollbackFor = Exception.class)
public OrderVO submitOrder(OrderSubmitDTO dto) {
// 校验门店配送范围
ScopeCheckVO scopeVo = shopScopeService.check(dto.getShopId(), dto.getAddressLng(), dto.getAddressLat());
if (!scopeVo.isPass()) {
throw new BizException(scopeVo.getMsg());
}
// 生成订单主记录
OrderDO order = new OrderDO();
order.setOrderNo(OrderNoUtil.gen());
order.setUserId(dto.getUserId());
order.setShopId(dto.getShopId());
order.setTotalAmount(dto.getTotalAmount());
order.setDeliveryFee(scopeVo.getDeliveryFee());
order.setStatus(OrderStatusEnum.WAIT_PAY.getCode());
order.setPayDeadline(LocalDateTime.now().plusMinutes(15));
orderMapper.insert(order);
// 写入订单明细
List<OrderItemDO> itemList = buildItem(order.getId(), dto.getCartList());
orderItemMapper.insertBatch(itemList);
// 投递订单创建事件
OrderCreateMsg msg = new OrderCreateMsg();
msg.setOrderId(order.getId());
rocketMQTemplate.send("order-create-topic", MessageBuilder.withPayload(msg).build());
return convertor.toVO(order);
}
}调度模块独立解耦,支持后台切换模式:抢单模式适合骑手数量偏少的初创阶段;自动派单适合运力充足的成熟平台。 调度逻辑基于Redis GEO索引检索范围内在线空闲骑手,综合距离、当前负载、准时率加权排序;骑手位置高频上报写入Redis,订单完结归档关键轨迹节点,降低存储压力。骑手取餐、送达节点支持验证码校验,防止虚假履约。
@Service
public class DispatchService {
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@Autowired
private RiderMapper riderMapper;
/**
* 检索附近空闲骑手
*/
public List<RiderMatchVO> matchRider(BigDecimal shopLng, BigDecimal shopLat, LocalDateTime orderTime) {
Set<Long> busyRiderIds = scheduleRedisService.getBusyRider(orderTime);
Circle circle = new Circle(new Point(shopLng.doubleValue(), shopLat.doubleValue()), new Distance(5, Metrics.KILOMETERS));
GeoResults<RedisGeoCommands.GeoLocation<String>> geoRes = redisTemplate.opsForGeo()
.radius("rider:geo", circle, RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance());
if (geoRes == null) {
return Collections.emptyList();
}
List<Long> riderIds = new ArrayList<>();
Map<Long,Double> distMap = new HashMap<>();
for (GeoResult<RedisGeoCommands.GeoLocation<String>> res : geoRes) {
Long riderId = Long.valueOf(res.getContent().getName());
if (busyRiderIds.contains(riderId)) continue;
riderIds.add(riderId);
distMap.put(riderId, res.getDistance().getValue());
}
List<RiderDO> riders = riderMapper.selectByIds(riderIds);
return riders.stream().map(rider -> {
RiderMatchVO vo = new RiderMatchVO();
vo.setRiderId(rider.getId());
vo.setName(rider.getRealName());
vo.setScore(rider.getScore());
vo.setDistance(distMap.get(rider.getId()));
return vo;
}).sorted(Comparator.comparing(RiderMatchVO::getDistance)).toList();
}
}商家模块支持商品多SKU、加料规格、库存管理;配送范围支持圆形围栏、多边形围栏后台地图绘制。营销规则统一配置:满减、优惠券、新客活动、时段折扣,结算时自动计算优惠,规则不硬编码。 结算采用延迟结算机制,订单完成后设置冻结期,冻结期内可处理投诉退款;冻结期满自动生成分账记录,平台抽佣规则按门店、活动类型差异化配置。定时对账任务自动比对订单表、支付流水、结算表,异常数据生成财务工单。

午晚高峰流量集中,热点商家、首页分类、商品数据预热进Redis;下单接口限流,按账号限制频次;数据库读写分离,查询走从库,订单写入走主库。 降级策略:高峰期关闭非实时统计报表、导出任务,保障下单、接单、骑手上报主链路;Redis GEO异常时自动降级为MySQL空间函数查询。 安全层面:用户、商家、骑手敏感信息脱敏展示;所有关键操作留存审计日志;支付回调、退款接口做好幂等防重。

这套四端一体外卖源码架构,依靠统一网关+角色权限体系隔离用户、商家、骑手、后台数据,订单状态机保障流转可靠,调度模块支持抢单/派单灵活切换,MQ与WebSocket完成四端消息协同。文中Java代码实现权限校验、订单创建、骑手匹配核心逻辑。架构兼顾初创低成本部署和后期扩容,业务模块解耦,运营侧的配送围栏、营销、抽佣规则全部后台可视化配置,适合同城外卖平台自主开发、二开迭代。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。