ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Java生鲜同城配送骑手系统源码解析:抢单并发与状态机实战

Java生鲜同城配送骑手系统源码解析:抢单并发与状态机实战 最近整理了一套 Java 构建的生鲜同城配送骑手系统全源码整个项目从需求梳理到代码落地都有完整链路。如果你正好在做 Java 后端开发想找一个真实业务场景练手或者需要一个能写进简历、面试时能讲清楚的项目这套源码里有几个值得慢慢啃的硬骨头——抢单并发怎么处理、订单状态机怎么设计、骑手位置高频率上报怎么做、WebSocket 推送怎么接。这篇文章我把这套系统的设计思路、核心实现、启动步骤和踩坑记录都拆开讲一遍内容比较长建议先收藏再慢慢看。1. 为什么要做这套骑手系统1.1 项目定位与业务价值生鲜配送和普通外卖最大的不同是时效要求更高蔬菜水果、肉禽蛋类这些商品在途时间稍长就容易导致品质下降所以整个系统围绕“快”字展开。骑手调度处于用户下单、商家出餐、网点配送这条链路的中间位置骑手接单是否迅速、路径是否合理、状态更新是否及时直接决定用户体验。我在整理这套源码的时候核心目标不是做一个看起来能跑的 demo而是把真实业务里的关键矛盾放进去。比如同一个订单被多个骑手同时抢、订单超时状态怎么兜底、骑手手机上不断上报的位置如何在服务端低延迟处理这些场景在培训班的练习项目里很少见到。从价值角度来看这套系统不仅仅是一个技术展示它对应的是即时配送领域里一套标准的业务模型骑手端、商家端、调度端、用户端四个角色在同一个平台里协同单看代码体量不算大但业务链条是完整的。1.2 技术选型背后的思考这套源码我选择的是 Spring Boot 2.x 单体应用 模块化分包而不是一上来就拆微服务。原因很直接订单、骑手、商家这些模块之间的调用关系是强耦合的单体架构能让业务闭环跑得更简单部署也轻。但我在分包时已经按模块边界切清楚了后面想要拆库拆服务成本不会太高。数据层用了 MyBatis-Plus没选 JPA。这里说下我的实际感受国内多数团队的 MySQL 使用习惯偏向 SQL 可控MyBatis-Plus 的 CRUD 能力强分页插件好用复杂查询写 XML 也方便溯源。缓存层用 Redis 处理热点订单数据、分布式锁和抢单计数消息队列选 RabbitMQ负责订单生命周期里各个节点的事件异步通知订单状态推送用 WebSocket 实现实时通信数据库选 MySQL 5.7 以上版本认证鉴权用 JWT Spring Security。技术栈的具体组成如下核心框架Spring Boot、Spring MVC、Spring Security数据层MyBatis-Plus、MySQL、Druid 连接池缓存与锁Redis、Lua 脚本消息队列RabbitMQ订单事件、短信通知异步化实时通信WebSocket骑手位置、订单状态推送定时任务Spring 自带的 Scheduled超时检测、无人接单兜底定位服务高德地图 API路径规划、距离计算、坐标转换这套组合在目前中小规模即时配送项目里是非常主流的搭配没有特别冷门的东西招聘市场上后端岗的技术栈重合度也比较高用它来做学习或者简历项目面试官看着不会觉得陌生。2. 系统架构与核心模块拆解2.1 整体架构与模块边界项目整体采用分层架构Controller 层、Service 层、Mapper 层、Entity 层界限分明。从业务模块上划分成五个核心部分用户模块用户注册登录、收货地址管理、订单下单入口商家模块商品维护、接单事件、菜品出餐状态管理骑手模块骑手注册认证、接单能力池、配送状态、轨迹上报、收入统计调度模块抢单池管理、订单分配策略、超时重新投放推送模块基于 WebSocket 的实时事件推送、消息中心模块之间通过 Service 接口交互避免跨 Controller 直接调用。比如用户下单后订单模块不直接操作骑手数据而是发出“订单已创建”的事件调度模块监听事件后把订单投放到骑手抢单池推送模块再把新单提醒发给符合条件的骑手。这样每个模块的职责单一出问题排查范围也小。目录结构我按功能分包组织而不是按技术职责分层包装所有类大概长这样com.example.fresh ├── common // 通用工具、常量、异常、状态枚举 ├── config // 配置类Redis、RabbitMQ、WebSocket、MyBatis ├── controller // 接口入口按模块拆分包 ├── service // 业务逻辑层 ├── mapper // 数据访问层 ├── entity // 数据库实体 ├── dto // 请求和响应模型 ├── job // 定时任务 └── push // 推送相关逻辑这样做的直接好处是将来替换某个第三方组件时改动面可控。比如推送模块如果要从原生 WebSocket 换成 Netty只需要在 push 包内部调整对外暴露的接口不变其它模块无感知。2.2 权限设计与角色边界系统共四种角色用户、商家、骑手、平台管理员。JWT 登录成功会返回一个 tokentoken 里只放用户 ID 和角色编码后续请求在拦截器里解析 token权限校验通过注解完成。我特别强调一个细节虽然这套系统叫“骑手系统”但权限模型不能只做骑手端因为订单流程里骑手要跟商家、用户产生交互如果没有完整的角色边界很多业务逻辑没办法闭环。比如骑手接单时要校验订单是否处于待接单状态、是否还在配送范围内而这些信息涉及到商家出餐状态和用户收货地址的数据读取。权限设计的实现思路是 Spring Security 的过滤器链 自定义鉴权注解后台管理接口强制要求管理员角色骑手端接口只开放给骑手角色用户端接口同理。源码里没有过度设计菜单权限和按钮权限没做数据库动态配置因为那会让项目复杂度上升一个量级真实小团队起步阶段也通常先用硬编码权限够用就行。2.3 核心业务流程与状态机生鲜配送订单从创建到完成要经过以下环节用户下单 → 商家接单 → 订单进入抢单池 → 骑手抢单成功 → 骑手到店取货 → 骑手送达 → 用户确认收货或系统自动确认整个流程中订单状态字段是关键中的关键。我采用的是状态机管理方式而不是在 Service 里散落到处 setStatus。先定义一个状态枚举CREATE已创建等待商家接单SELLER_ACCEPT商家已接单等待骑手接单RIDER_ACCEPT骑手已接单DELIVERING骑手配送中COMPLETED已完成CANCELED已取消TIMEOUT超时挂起状态机的核心约束是任何状态的跳转必须走一个集中转移规则非法跳转直接抛异常。举个例子订单从 CREATE 可以直接跳 CANCELED但不能直接跳 COMPLETED。负责转移的方法里会通过一个转移表判断当前状态和目标状态是否允许转变这比在每个 Service 方法里手工判断要可靠得多尤其多人协作时后面接手的人不会因为漏判断把流程搞乱。我贴一段状态机核心逻辑的简化版本public enum OrderStatus { CREATE, SELLER_ACCEPT, RIDER_ACCEPT, DELIVERING, COMPLETED, CANCELED, TIMEOUT; private static final MapOrderStatus, SetOrderStatus TRANSITIONS new EnumMap(OrderStatus.class); static { TRANSITIONS.put(CREATE, EnumSet.of(SELLER_ACCEPT, CANCELED, TIMEOUT)); TRANSITIONS.put(SELLER_ACCEPT, EnumSet.of(RIDER_ACCEPT, CANCELED, TIMEOUT)); TRANSITIONS.put(RIDER_ACCEPT, EnumSet.of(DELIVERING, CANCELED, TIMEOUT)); TRANSITIONS.put(DELIVERING, EnumSet.of(COMPLETED, TIMEOUT)); TRANSITIONS.put(TIMEOUT, EnumSet.of(RIDER_ACCEPT, CANCELED)); TRANSITIONS.put(COMPLETED, EnumSet.noneOf(OrderStatus.class)); TRANSITIONS.put(CANCELED, EnumSet.noneOf(OrderStatus.class)); } public boolean canTransitionTo(OrderStatus target) { return TRANSITIONS.get(this).contains(target); } }实际业务里 TIMEOUT 状态的订单会重新进入抢单池给其它骑手再次抢单的机会这就是“超时重新投放”的实现基础。状态机保证了异常分支不会把订单流转到不该去的位置比如一个已经被取消的订单绝不会突然进入配送中。3. 关键环节实现从源码构建到本地跑通3.1 环境准备与依赖工具源码构建的第一步不是急着启动 Spring Boot而是把基础环境准备好。我列一下我在本地验证过的版本组合JDK 8 或 11建议 8兼容性最好Maven 3.6 以上MySQL 5.7 或 8.0Redis 5.0 以上RabbitMQ 3.8 以上自带管理插件更方便很多初学者跑不起来项目不是因为代码有问题而是版本不匹配。比如 JDK 17 跑 Spring Boot 2.3 会遇到各种反射类访问报错MySQL 8.0 又默认使用 caching_sha2_password 认证插件老版本的驱动连不上。源码里如果用 mysql-connector-java 8.0.x配合 MySQL 8.0 是没问题的但如果你本地是 MySQL 5.7就要注意配置里是否指定了 useSSLfalse 和 serverTimezone否则启动时连接池初始化就报错。Maven 依赖下载慢的问题在国内基本是标配。我在源码的 pom.xml 里建议配置阿里云镜像仓库配置方式是在你的全局 Maven settings.xml 里加 mirrormirror idaliyunmaven/id mirrorOfcentral/mirrorOf name阿里云公共仓库/name urlhttps://maven.aliyun.com/repository/public/url /mirror3.2 数据库初始化与关键表结构源码根目录一般会带 sql 目录里面包含完整建库脚本。执行顺序是先创建数据库 fresh_delivery再按依赖关系导入表最后补测试数据。我直接把最核心的几张表的 DDL 结构拿出来讲讲这些表是理解整个系统的钥匙订单表是核心中的核心字段包含订单号、用户 ID、商家 ID、骑手 ID、订单状态、商品快照、配送地址、配送距离、预计送达时间、实际送达时间、支付状态、创建时间、更新时间。关键索引有两个一个是订单号的唯一索引一个是联合索引状态 创建时间用来查询超时订单、调取抢单池列表。骑手表除了基本信息外还包含当前经度、纬度、接单状态在线/离线/配送中、今日完成单量、评分、接单半径配置。骑手位置表独立拆分因为位置数据时效性很强不属于骑手的静态资料单独存可以避免频繁更新主表造成锁竞争。订单状态日志表也很重要每次状态变更插入一条流水记录状态从什么变成什么、操作人是谁、变更原因。排查线上问题时这张表能帮你在没有直接证据的情况下还原整个事件的先后顺序。我还做了消息推送记录表记录 WebSocket 推送的消息内容和推送状态防止推丢了无法追踪。这一点小设计在面试时提出来很加分说明你有线上排查意识。核心 DDL 的简化版本大致如下CREATE TABLE order ( id bigint(20) NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 订单号, user_id bigint(20) NOT NULL COMMENT 用户ID, seller_id bigint(20) NOT NULL COMMENT 商家ID, rider_id bigint(20) DEFAULT NULL COMMENT 骑手ID, status varchar(30) NOT NULL COMMENT 订单状态, amount decimal(10,2) NOT NULL COMMENT 订单金额, delivery_address varchar(255) NOT NULL COMMENT 配送地址, distance_meter int(11) NOT NULL COMMENT 配送距离米, expect_time datetime NOT NULL COMMENT 预计送达时间, actual_time datetime DEFAULT NULL COMMENT 实际送达时间, create_time datetime NOT NULL, update_time datetime NOT NULL, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_status_create_time (status, create_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;3.3 启动流程与配置关键点环境准备好之后启动步骤我按顺序来理一遍。第一步启动 MySQL、Redis、RabbitMQ 三个基础服务。RabbitMQ 默认有个 guest 账号但 guest 只能在本地使用如果你通过远程连接需要新建用户并授权很多人在项目启动时报 RabbitMQ 连接拒绝十有八九是账号权限问题。第二步导入数据库脚本。注意脚本里如果有存储过程或者触发器直接用命令行导入比用图形化客户端更稳。第三步修改 application.yml。需要确认的核心配置有四块数据源URL、账号、密码Redishost、port、passwordRabbitMQhost、port、vhost、账号密码JWT签名密钥、token 过期时间特别提醒两句。数据源 URL 里一定要加 serverTimezoneAsia/Shanghai不加的话 MySQL 8 的系统时区匹不上控制台直接报 UTC 时间差错误。JWT 的签名密钥绝对不能用源码里默认的那一个默认密钥只是方便演示一定部署前要换掉否则别人拿着生成 token 的算法可以直接伪造身份。第四步启动项目。有两种方式直接跑 Application.java 的 main 方法或者用 Maven 命令mvn clean package -DskipTests java -jar target/fresh-delivery.jar这套代码是 Maven 多模块还是单模块取决于你拉到的版本如果是多模块记得在根目录执行 mvn install 把 common 模块先装到本地仓库否则其它模块编译时找不到依赖。第五步验证接口。项目启动成功后可以访问健康检查接口确认状态。然后按业务流程走一遍注册用户、登录拿 token、下单、商家接单、投放抢单池、骑手抢单、骑手配送、送达完成。每一步的返回都符合预期系统基本就是通的。4. 骑手系统的高频难点与解决思路4.1 抢单并发控制抢单是整个系统里最容易出事的地方。一个订单进入抢单池后可能有几十上百个骑手同时抢同一个订单如果控制不好要么出现重复分配要么出现超卖一样的逻辑错误。我先说最基础的方案数据库乐观锁。直接在更新订单状态时带上条件UPDATE order SET rider_id #{riderId}, status RIDER_ACCEPT WHERE id #{orderId} AND status SELLER_ACCEPT这条 SQL 能保证同一时间只有一个骑手能把状态从 SELLER_ACCEPT 改成 RIDER_ACCEPT因为是数据库层面行锁保证的。但这个方案在高并发下大量请求会同时在行锁上等待MySQL 内部线程调度开销很大极端情况下订单多了会把数据库拖垮。而且有些团队会把订单状态放到 Redis 里那 SQL 这套就没法直接用了。我在这套源码里用的是 Redis 分布式锁 Lua 脚本组合方案核心思路是第一道关口用 Redis 锁防止大量请求直接打到数据库锁的粒度是按订单 ID 加锁不是全局锁这样不同订单的抢单互不干扰。获得锁之后再做状态校验、更新。释放锁时用 Lua 脚本保证原子性先比较锁的 value 再删除防止误删别人持有的锁。锁的过期时间要特别用心。我曾经踩过一个坑把锁过期时间设成 10 秒以为抢单很快能完成没想到一次订单力度校验还调了高德接口计算配送距离耗时 12 秒锁先过期了第二个骑手的请求拿到锁也通过了校验最终两个人都以为抢到了。后面我把抢单拆成两段锁内只做 Redis 状态预占和快速的本地校验数据库落库移到锁外配合乐观锁兜底彻底解决这个问题。锁内不要做远程调用这句话值得刻在显示器上。抢单核心逻辑的简化代码// 对订单维度加锁 String lockKey order:lock: orderId; String lockValue UUID.randomUUID().toString(); Boolean acquired redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 10, TimeUnit.SECONDS); if (!Boolean.TRUE.equals(acquired)) { throw new BizException(手慢了订单已被抢); } try { // 在锁内只做订单状态预校验和预占 String orderStatus redisTemplate.opsForValue().get(order:status: orderId); if (!SELLER_ACCEPT.equals(orderStatus)) { throw new BizException(订单状态已变化); } // 预占标记抢单者防止其它骑手重复抢 redisTemplate.opsForValue().set(order:grab: orderId, riderId, 30, TimeUnit.SECONDS); // 异步落库返回抢单受理结果 dispatchService.asyncConfirmOrder(orderId, riderId); return 抢单成功请准时到店取货; } finally { String currentValue (String) redisTemplate.opsForValue().get(lockKey); if (lockValue.equals(currentValue)) { redisTemplate.delete(lockKey); } }注意这里的 try-finally 里释放锁时又读了一次 value这里不是多余动作就是为了防止锁过期后别人新建了锁你直接 delete 把别人的锁误删了那就踩了大坑。除了分布式锁我还配了 Redis 预减或者叫抢单令牌桶的思路。订单进入抢单池时先在 Redis 里设置一个抢单计数 key值设为 1抢单请求先进来先用 Lua 脚本做原子扣减扣减成功才走后续逻辑。这样大部分无效请求在进入业务校验之前就被挡掉了数据库压力其实很小。异常情况也要考虑骑手抢单成功但三分钟内未到店取货系统要自动取消并重新投放订单。这个机制靠定时任务扫 Redis 超时键实现而不是扫数据库因为数据库扫描量大且延迟高。4.2 订单超时与状态补偿生鲜配送对超时特别敏感。用户订单超过预计送达时间系统要主动感知并处理不能干等着。源码里设计了三层保障。第一层是用户下单时写入预计送达时间骑手接单后开始倒计时快到时间时推送提醒给骑手。这里用的是 RabbitMQ 的延迟队列不是定时轮询数据库。延迟队列的优势是精确到单条消息不需要全表扫描压力小很多。第二层是状态机的 TIMEOUT 状态兜底。如果延迟队列的消息触发了超时处理订单状态会流转到 TIMEOUT此时订单从骑手名下释放重新投放到抢单池。这层设计的巧妙之处在于不是直接把订单取消而是给它第二次机会因为在真实场景里可能是骑手路上堵了重新派单更合理。第三层是定时任务全量补偿。为什么有延迟队列还要定时扫因为队列的消息可能丢失或者服务重启期间消息被吞了。我用 Spring Scheduled 写了一个每五分钟跑一次的补偿任务扫描所有处于 DELIVERING 状态并且超时时间超过阈值仍未送达的订单强制触发超时处理。定时任务和延迟队列是一主一备的关系不会重复处理因为处理逻辑做成了幂等判断当前状态必须合法才能流转。这套三层策略让我后来线上处理超时问题时特别省心不会出现“不知道哪个订单超时了”的慌乱局面。4.3 骑手位置上报与轨迹链路骑手的 App 端每三到五秒会上报一次经纬度坐标如果每秒上报一次一个骑手一天就是几万条记录上百个骑手的数据量就不小了。全量实时写数据库完全不现实所以我把位置上报链路做成了两级缓存加批量落库。骑手调用上报接口后服务端先把坐标写入 Redis 的 zset 结构按骑手 ID 作为 keyscore 用时间戳value 是 JSON 字符串包含经纬度和精度。管理后台需要看实时位置时直接读 Redis 最新一条数据几百毫秒内就能看到骑手位置刷新。另一个定时任务每十秒从 Redis 里批量取出上报记录一次性写入位置历史表再清理 Redis 里的旧数据。这里有一个坐标系的问题特别容易忽略。高德地图用的是 GCJ-02 坐标系而部分硬件或者第三方定位 SDK 返回的是 WGS-84 原始坐标。如果直接拿原始坐标在高德地图上展示位置会偏几十米到几百米。骑手定位经度纬度存库时要统一转成 GCJ-02或者前端展示时统一做转换不然后端计算距离、前端标点完全是两套标准看起来是同一位置实际相差几条街。实现里我封装了一个 CoordinateConverter 工具类统一处理 WGS-84 和 GCJ-02 的互转写入前先做一次坐标校验超出中国范围或者经纬度明显异常的直接丢弃。这样不仅保证数据质量还能防止脏数据污染统计报表。4.4 WebSocket 实时推送架构推送模块的设计用在了三处骑手抢单成功通知、订单状态变化通知、商家接单提醒。原生 WebSocket 在 Spring Boot 里配置简单性能在几千连接规模下没问题真要几十万长连接你再上 Netty。WebSocket 接入的关键是鉴权。WebSocket 握手阶段浏览器不能自动带 Authorization Header通常会通过 URL 参数传 token但 token 放在 URL 里会被日志记录有泄露风险。我采用的是二次校验方案连接建立时先用连接参数里的 token 做一次快速校验连接建立后客户端再发一条鉴权消息服务端在 onMessage 里校验通过才订阅对应用户的主题没有通过直接关闭连接。这个方案安全性和灵活性都更优。每个角色订阅的主题不同。用户订阅 /topic/user/{userId}骑手订阅 /topic/rider/{riderId}商家订阅 /topic/seller/{sellerId}。推送模块做了路由分发订单状态变化时只推送给相关方不全局广播。WebSocket 如果和 Spring Security 一起用老版本经常会遇到拦截器不生效的问题。我这里直接统一在 WebSocket 的拦截器里做 token 解析和用户身份绑定不走 Spring Security 的过滤器链少了框架之间的集成坑排查问题也直观。5. 常见问题排查与避坑实录5.1 问题速查表源码项目比纯文档更能暴露真实环境的问题。我把启动和运行过程中最常见的几个问题整理成了一张速查表基本覆盖了新手阶段能遇到的大部分坑。现象可能原因解决思路启动时端口被占用8080 被其它进程占用查看占用进程换端口或在启动参数指定 --server.portRedis 连接超时Redis 未启动、网络隔离、密码错先 redis-cli ping 验证再看配置的 host/port/passwordRabbitMQ 无法登录guest 账号远程限制新建账号并授予 vhost 权限连接配置相应修改MySQL 驱动连接报错版本不匹配或时区未设置pom 中驱动版本与 MySQL 版本对应URL 加 serverTimezoneAsia/Shanghai订单状态没有更新乐观锁条件不满足检查更新 SQL 的 status 条件打印受影响行数抢单偶尔重复锁过期时间太短或未续期锁内不做远程调用配合数据库乐观锁兜底WebSocket 频繁断开未做心跳检测服务端定时发送 ping超时未响应主动关闭连接高德坐标偏移WGS-84 与 GCJ-02 混用统一调用坐标转换工具类再存储或展示编译时找不到 common 模块多模块未 install在根目录执行 mvn install接口返回 401JWT 过期或 secret 不一致确认 token 有效期检查各服务 secret 配置是否一致这张表我自己在整理时把高频问题都过了一遍很多同学在群里问的问题其实就是上面某一个。建一个排查清单比遇到问题再翻源码快得多。5.2 踩过的坑和真实教训我印象最深的一个坑是抢单锁过期时间的问题。上文提到过一个场景我在锁内调用了高德 API 来计算骑手到商家的距离这个远程调用在网络抖动时耗时特别长最差一次到了 15 秒而锁的过期时间只有 10 秒。结果锁过期后另一个骑手进来抢单也通过了状态校验拿到了同一单数据库层虽然靠乐观锁把最终写库挡掉了但用户端收到了两条“抢单成功”的推送。这个问题的根因不是乐观锁没有兜底成功而是锁内做了一个不该做的远程调用和锁过期时间设置不合理。后来我的调整方式就是把锁内操作压缩到最薄只保留本地内存判断和 Redis 操作远程调用全部挪到锁外在异步执行里再做校验就算校验失败也有补偿机制回滚。还有一次教训是关于状态机的。最初代码里骑手确认送达时直接就把状态改成了 COMPLETED没有经过 DELIVERING 校验。结果线上有个问题用户取消的订单骑手端还能操作确认送达因为 Service 层没有统一的状态校验只有一个 if 判断漏了取消场景。这个 Bug 修复起来不复杂但排查过程很曲折。后来我意识到状态机这样的关键逻辑一定要集中管理不能让各个业务方法自己写状态判断否则新增业务分支时遗忘就能造成灾难。这也是为什么源码里我把状态转移表作为核心设计单独抽出来的原因。还有个比较小的坑但也想提醒大家。MySQL 的时间字段和 Java 的 LocalDateTime 之间如果类型映射不当查询出来时间对不上。用 MyBatis-Plus 时实体字段类型用 LocalDateTime配合 MySQL 的 datetime 类型同时数据库连接串加 useSSLfalse 和 characterEncodingutf8基本不会出问题。反过来如果实体用 java.util.Date配合的时区处理就会啰嗦我统一推荐 LocalDateTime尤其是在做订单超时计算时LocalDateTime 的 API 比 Date 好写太多了。6. 源码阅读路线与扩展建议6.1 合理的源码阅读顺序拿到这套源码我的建议不是从第一个类按顺序往后读而是按业务链路去读效率会高很多。第一步先看数据库建表脚本。读表是最快的了解业务的方式拿到订单表、骑手表、商家表后心里对业务模型就有基本轮廓了。第二步看启动类和全局配置了解系统开了哪些功能、集成了哪些组件。第三步选一条完整业务链路从用户下单的 Controller 入口进去一路追到 Service、Mapper体验一下整个调用链是怎么流转的。有了这条主线的上下文再去看骑手抢单、配送、推送这些分支逻辑就不会觉得特别抽象。在看代码时我有个小技巧可以重点看状态机枚举类、抢单 Service 实现类和推送模块的路由类。这三个文件基本涵盖了系统的核心设计思想读懂了它们很多散落的业务逻辑都能自动串起来。6.2 后续可扩展的方向这套系统虽然是完整的但放到真实生产环境还有不少可以扩展的地方。比如当前的调度策略比较简单骑手接单范围是按固定半径判断的实际调度平台会结合骑手实时位置、路径拥堵情况、订单重量做综合评分。这可以做为第二期的优化点。另一个方向是缓存和数据库一致性。当前订单状态同步更新 Redis 和 MySQL严格说存在短暂不一致的可能。你可以引入 Binlog 监听或者本地消息表模式把这个话题变成面试中很有深度的项目亮点。扩展技术栈方面可以试着把单体模块拆分出来。订单模块和推送模块拆成两个独立的 Spring Boot 服务用 OpenFeign 或者 Dubbo 做远程调用体验一下微服务改造过程中要面对的数据一致性、链路追踪等真实问题。从学习成长角度看把这套代码跑通只是第一步试着改一两个功能、加一张表、写一个接口才算真正把它消化成自己的东西。就我个人经验来说拿到任何全源码项目先不要急着跑起来先花半小时把数据库表结构和状态流转读一遍这个时间投入非常值得。后面你调试起来心里有张地图知道每一步数据应该在哪、状态应该是什么遇到问题定位快得多。这套骑手系统我建议你重点研究抢单并发和状态机两块它们是整个项目里最有面试价值的部分。如果中间有任何跑不通的地方先对照上面那张速查表大部分问题都能解决。
返回列表