☰
SpringCloud+Vue+小程序:校园外卖配送系统微服务架构全栈实战解析
2026/10/5 3:11:42 网站建设 项目流程

1. 项目概述

1.1 这是一套什么系统

这是一个面向校园场景的外卖美食配送平台,技术栈是SpringBoot + SpringCloud + Vue + 微信小程序。用户端通过小程序下单点餐,商家端在后台管理菜品和订单,配送端由骑手(快递员)接单取餐送餐。整个系统采用微服务架构,不是传统的一体化单体应用,而是把用户、订单、商家、配送、支付这些核心模块拆成独立服务,各跑各的进程,通过注册中心、网关、消息队列串联起来。

很多读者看到"微服务"三个字就有点发怵,觉得只有大厂才用得上。但校园外卖这个场景其实非常适合用微服务来讲清楚问题:业务链路长(用户下单、商家接单、骑手配送、订单状态流转)、并发有明显峰值(饭点是集中流量)、角色多(学生、商家、骑手、平台管理员)、终端多(小程序、管理后台、骑手App端)。这些特征刚好是微服务能发挥优势的地方,比单纯做个Demo有意义得多。

这个项目的直接价值有两个层面。对学生开发者或初中级工程师来说,它是一套完整的、可落地的全栈微服务实战范本,能帮你把SpringCloud全家桶里那些零散组件——Nacos、Gateway、OpenFeign、Sentinel——串成一条真实的业务线。对学校或园区运营方来说,它是一个可以直接二次开发上线的配送平台基础版本,连骑手调度这种最容易踩坑的模块都预留了设计空间。

1.2 这套系统要解决的三个核心问题

校园外卖平台表面上看起来就是"外卖App的简化版",但真正动手做起来,有三个问题决定了项目的成败。

第一,微服务拆分的粒度怎么定。拆太细,比如把菜品和库存都拆成独立服务,带来的问题是服务间通信频繁、事务一致性难保证,对一个小团队来说运维成本直接压垮开发进度。拆太粗,比如只拆一个"业务服务"加一个"用户服务",那微服务就名存实亡了。这个项目采用的拆分思路是"业务域优先"——按用户、商家、订单、配送四个核心业务域拆,辅助以网关、认证等基础设施服务,这个粒度对校园场景来说是相对稳妥的选择。

第二,订单状态在多个服务之间怎么流转。一个订单从"待支付"到"已支付"到"商家已接单"到"骑手已取餐"到"已完成",这中间涉及用户服务扣款、订单服务改状态、商家服务通知、配送服务派单。如果不做细致的状态机设计和消息通知机制,订单状态很容易错乱。后面我会详细讲这一块的实现方案。

第三,骑手配送这个环节怎么设计才不鸡肋。很多外卖项目的骑手端只是做个简单的列表页面,接单按钮一写就完事了。但实际上骑手端涉及抢单、配送中位置更新、订单状态回传、收益统计等多个交互,而且要考虑到骑手手机网络不稳定的场景。这个项目里我对骑手端的定位是"轻量但完整",核心闭环必须跑通。

2. 技术选型与架构设计

2.1 为什么是SpringCloud而不是SpringBoot单体

先回答一个最容易被问的问题:一套校园外卖系统,用SpringBoot单体应用做,两三个星期就能上线,为什么要引入SpringCloud全家桶,徒增复杂度?

我的回答是:看你的目标是"交付一个软件"还是"掌握一套方法论"。如果纯粹是想快速搞个外卖站,单体确实够了。但这个项目的定位是分布式架构实践,SpringCloud的核心价值不在"能不能跑通功能",而在"业务规模扩大之后系统还能不能优雅地演进"。

用单体应用举一个具体例子:用户下完单之后,订单服务要扣库存、要通知商家、要记录配送信息。如果库存和商家功能都在同一个进程里,方法调用就行,很爽。但等到促销活动来了,订单量一涨,你得水平扩展部署多个实例。这个时候问题就来了——单体应用里所有功能耦合在一个进程,你只能整体扩容,而整个系统里最吃资源的可能是订单模块,用户模块根本没压力。微服务把这个痛点解决得很干脆:订单服务单独部署三副本,用户服务单副本,按需扩容,互不干扰。

另外,微服务对团队协作也有实际价值。开发时用户端、订单端、配送端的代码仓库是分开的,不同人负责不同模块,合并冲突少了很多。我记得做这个项目的时候,三个人并行开发,用户服务只跟订单服务有OpenFeign接口约定,商家服务跟订单和配送服务有接口约定,只要把接口定义好,各写各的基本不用互相等。

2.2 核心技术组件选型与理由

整个系统的组件选型遵循一个原则——生产环境验证过的、生态成熟的、资料多的。毕竟不是造轮子项目,没必要选冷门技术来彰显个性。

组件选型核心理由
注册中心与配置中心Nacos同时搞定服务发现和配置管理,不用额外引入两套组件;中文文档全,遇到问题好排查
微服务网关Spring Cloud Gateway官方出品,路由能力、过滤器机制、限流配置都比较完善,性能比Zuul好一截
远程调用OpenFeign声明式HTTP客户端,写接口调用就像写本地方法,维护成本低
服务熔断降级Sentinel配置控制台可视化,流控规则实时生效,比Hystrix的维护状态更有优势
认证鉴权JWT + Spring Security无状态认证,适合微服务场景,网关统一校验Token
前端后台Vue 3 + Element Plus组件库成熟,表格/表单等后台常用组件开箱即用
用户端微信小程序校园场景学生高频使用微信,小程序免安装、分享方便
数据库MySQL 8.x + RedisMySQL存储业务数据,Redis处理热点与缓存
消息队列RabbitMQ订单状态变更、通知推送削峰填谷,轻量不重

这里重点说一下Nacos的选择。早期微服务项目里Eureka作为注册中心很常见,但Eureka已经进入维护模式,而且它只是个注册中心,配置中心还得另外搭Spring Cloud Config。Nacos一个组件同时解决两个问题,服务启动后既能注册也能拉配置,控制台还带命名空间管理,不同环境(dev、prod)切换配置很方便。对中小型项目来说,少一个组件就少一份运维负担,这是很实在的好处。

2.3 微服务拆分边界与服务间协作拓扑

本项目的微服务划分,从业务域和复用角度出发,拆成以下核心服务:

用户服务:处理C端用户注册、登录、地址管理、用户余额。校园场景可以加一个"学生认证"的字段,不过初期版本用微信OpenId做账号主体就够。

商家服务:管理商家入驻信息、店铺信息、菜品分类、菜品上架下架。商家后台需要的所有数据都从这个服务出。

订单服务:整个系统的核心服务,承担下单、订单查询、订单状态流转、订单超时处理。这个服务的并发压力最大,也是分布式事务和缓存策略最集中的地方。

配送服务:负责订单与骑手的匹配、骑手接单、配送位置轨迹上报。校园场景配送范围小,距离计算直接用经纬度球面距离公式就行,不需要引入高德地图API的路径规划。

网关服务:所有请求的统一入口,负责路由转发、JWT Token校验、接口限流。

这些服务之间的协作方式,就是通过OpenFeign进行同步调用,通过RabbitMQ进行异步解耦。同步调用用于"必须立刻知道结果"的场景,比如商家服务查询菜品详情,用户服务扣减余额。异步消息用于"可以稍后处理"的场景,比如订单创建之后通知配送服务创建配送单、通知商家服务有新的订单提醒。

这么一拆,边界就清晰了:订单服务不直连商家表,商家服务不直连用户余额表,所有跨服务的数据都通过接口或消息获取。这避免了微服务架构中最常见的"分布式单体"问题——服务没拆干净,A服务直接操作B服务的数据库表。

3. 小程序端与Vue后台的落地实现

3.1 微信小程序端:从页面结构到接口联调

很多做小程序的开发者一开始会纠结:用原生微信小程序还是用uni-app?这个项目里我选择原生写法。原因很简单——项目里的小程序端只服务一个场景(学生点餐),没有多端复用需求,用uni-app反而多一层编译转换开销。不过如果你后续要把骑手端也做成小程序,或者考虑支付宝小程序,那uni-app是更好的选择。

小程序的页面结构围绕用户核心动线设计:

pages/ ├── index/ // 首页:轮播图、商家列表、平台公告 ├── shop/ // 店铺页:店铺信息、菜品列表、商品规格弹窗 ├── cart/ // 购物车页:已选菜品、合计价格、结算入口 ├── order/list/ // 订单列表页:待付款、待收货、已完成等状态Tab ├── order/detail/ // 订单详情页:订单状态、商家信息、骑手信息 ├── address/ // 地址管理页:学校地址列表、新增编辑 └── profile/ // 个人中心:用户信息、余额、联系客服

订单列表页的"加载更多"这个功能,是所有小程序初学者最容易写崩的地方。核心逻辑是这样的:请求订单列表时带上pageNum和pageSize两个参数,后端返回数据时分页查询。小程序端用一个onReachBottom事件,页面滚动到底部时自动触发下一页加载,同时用loading状态防止重复请求。我在这个项目里的做法是在data里维护一个loading字段和finished字段,每次请求前判断,请求完成后如果返回的list长度小于pageSize,就说明没有更多数据了,把finished置为true,不再发请求。这个逻辑看着简单,但如果不加防重复请求的判断,用户在页面底部快速滚动时,一次能触发三四次重复请求,订单列表会明显出现卡顿和重复数据。

接口联调阶段要注意小程序的域名校验问题。本地开发时在开发工具里勾选"不校验合法域名"就行,但上线前必须配置request合法域名。提前把后端网关的地址规划好很有必要,比如所有接口统一走https://api.xxx.com/order/...这样的前缀,小程序端只需要封装一个request工具函数,自动拼接baseURL和处理Token。

3.2 Vue管理后台:商家与平台管理如何兼顾

管理后台用Vue 3 + Element Plus实现,这里有一个设计取舍值得说一下——后台要同时满足两种角色:平台管理员和商家经营者。两个角色看到的界面有交集但也有差异。平台管理员要看所有商家、所有订单、平台总流水,可以审核商家入驻申请、下架违规菜品。商家经营者只能看自己的店铺数据、订单和菜品。

实现这种权限模型的方案是这样的:后台登录接口返回的JWT Token里包含role字段,前端路由守卫根据角色做动态路由注册。平台管理员登录后注册全部路由,商家登录后只注册商家相关的路由。接口层面网关会再次校验角色权限,只有role=admin的请求才允许访问/admin/**路径,防止有人直接拼URL越权访问。

后台的订单管理页面,我做了几个实用功能:按订单状态筛选、按时间段筛选、订单金额合计。表格里展示订单编号、用户昵称、商品明细摘要、实付金额、下单时间、当前状态。订单详情页用抽屉组件展开,能看到完整的菜品列表、收货地址、配送信息和时间线记录。时间线记录这个功能很重要,后端在订单状态每次变更时都写入一条日志,后台按时间正序展示,这对排查"订单为什么卡在某一状态"非常有帮助。

Vue后台的环境配置也有值得记录的经验。开发环境用vue.config.js配置devServer的proxy代理,把/api前缀的请求转发到网关地址,避免跨域问题。生产环境则通过gateway统一配置跨域,前端直接把请求发到网关域名。这里有个坑,生产环境前后端分离部署后,如果网关没有配置CORS,浏览器会直接拦截请求。在网关的全局CORS过滤器里加好Access-Control-Allow-Origin和Access-Control-Allow-Headers,一个下午就能搭好后端联调环境。

3.3 小程序端常见问题:定位、登录态与缓存

小程序端做下来,有三个高频问题值得展开写一写。

第一个是微信登录的临时code换OpenId流程。小程序的wx.login()拿到的code是临时凭证,有效期只有几分钟,必须通过后端接口去微信的接口服务换取OpenId和SessionKey。正确的流程是:前端拿到code后传给后端user服务,user服务用code调微信接口换OpenId,然后查数据库——如果OpenId存在就返回登录成功,不存在就自动注册新用户再返回。前端把后端返回的自定义Token存在storage里,后续请求带上这个Token。整个流程不要在前端处理AppId和Secret,这些敏感信息必须放在后端,否则小程序代码包被解包后密钥直接泄露。

第二个是收货地址定位不准。校园场景的地址主要是宿舍楼、教学楼、食堂这种POI点,直接用微信的wx.chooseLocation选择地址是可以的,但返回的经纬度是百度系坐标,如果后端用的是高德地图或者自己的距离计算,坐标系不统一会导致位置偏移几十米甚至几百米。实际项目里我的处理方式是:用户选择位置后,前端用微信的wx.translateCoordinate做坐标转换,再传给后端存储。如果后端直接用腾讯地图SDK,那就不需要转换,因为微信授权位置返回的本来就是腾讯系坐标。

第三个是小程序包体积优化。校园WiFi环境下包体积太大影响加载速度也是真实痛点。整个小程序包尽量不要超过2MB,超过之后微信会要求分包加载。我把常用的图标做成字体图标或CDN图片链接,而不是放在本地静态资源里;图片全部走CDN,商品图片链接存在数据库里,不打包进小程序代码。购物车和订单列表的图片用懒加载,image组件加上lazy-load属性,页面滚动时再加载图片资源。

4. 核心业务流程与关键实现

4.1 从下单到支付:一次完整订单的流转

整个系统最核心的业务流程,就是用户从浏览商家到下单支付完成的过程。这条链路跨越了三个微服务,状态流转极其容易出错,我把每一步的细节拆开讲。

Step 1:浏览店铺与菜品

用户进入首页,小程序端调用网关的GET /api/shop/list接口,网关路由转发到商家服务,商家服务返回店铺列表(包含起送价、配送费、评分、月销量)。用户点击店铺后,调用GET /api/shop/{shopId}/menu,返回菜品分类和菜品详情。这里菜品列表需要实时展示"已售罄"状态,所以每次请求都会实时查询库存表,不走缓存。菜品图片走CDN,详情里包含规格(大份/中份/小份),价格和库存都挂在规格下面。

Step 2:加入购物车与提交订单

购物车在小程序端是本地维护的,用Storage存储已选菜品和数量,不调后端接口,这样用户体验最流畅。点击结算时,前端把购物车数据组装成订单创建请求,调用POST /api/order。这个请求经过网关转发到订单服务,订单服务的核心逻辑是:

  1. 验证商家是否营业、菜品是否在售、库存是否充足。
  2. 从购物车数据中计算总金额,加上配送费,生成订单金额。
  3. 调用用户服务接口扣减用户余额(或校验支付能力),在扣减之前先查余额是否足够。
  4. 扣减库存——注意这里是关键点,库存不在订单服务里,所以要通过OpenFeign调用商家服务的扣库存接口。
  5. 订单状态置为PENDING_PAYMENT,返回订单ID和预支付参数。

Step 3:发起支付

这个版本里我接的是模拟支付的流程。订单创建成功后,前端弹出支付确认框,点击确认后调用POST /api/order/{orderId}/pay。订单服务收到支付请求后,调用支付中心接口(模块内部模拟),支付成功后把订单状态改为PAID。

Step 4:异步通知商家和创建配送单

这里就是前面提到的事件驱动的用武之地。订单服务在订单状态变成"已支付"后,发布一个OrderPaidEvent到RabbitMQ的消息队列。商家服务监听这个事件,给商家管理后台推送一条"新订单待接单"提醒。配送服务同时监听事件,创建一条配送任务记录,状态为WAITING_RIDER,等待骑手抢单。

这个设计的好处是订单服务不需要同步等待商家服务和配送服务的处理结果,把一次支付请求的响应时间降到了最低。哪怕配送服务当时出了故障暂时不可用,订单也已经成功支付,等配送服务恢复后再补发配送任务也不会导致丢单。

4.2 状态机设计:订单状态如何保证不乱

整个流程中订单状态有:PENDING_PAYMENT(待支付)、PAID(已支付)、ACCEPTED(商家已接单)、PICKED_UP(骑手已取餐)、DELIVERED(已送达)、COMPLETED(已完成)、CANCELLED(已取消)。

这些状态不是随意流转的,我在订单服务里用枚举 + 状态流转表做了严格约束。实现方式不复杂:定义一个OrderStatusChange的Map,key是当前状态,value是允许的下一状态集合。每次状态变更时先校验——如果当前状态不允许直接跳到目标状态,就抛异常。举个例子,PENDING_PAYMENT状态允许跳转到PAID或CANCELLED,但PAID状态不能直接跳COMPLETED,必须经过ACCEPTED和PICKED_UP。

这套约束在分布式环境里尤其重要。因为订单状态变更的接口可能被多个角色触发:用户主动取消、商家接单、骑手取餐。如果没有状态机校验,可能会出现"用户取消订单"和"商家接单"两个并发请求同时到达,后到的操作覆盖了先到的,导致一个已取消的订单变成了已接单状态。除了状态机校验,我还在订单操作接口上加了乐观锁(版本号机制),防止并发更新时出现脏写。

4.3 骑手接单与配送闭环:位置与轨迹

骑手端是这个项目里功能性很强的模块,骑手角色跟普通用户不一样,核心动线是:抢单 → 到店取餐 → 确认取餐 → 送达完成。这四步对应四个状态操作,每个操作都要调配送服务更新订单配送状态。

骑手接单这个环节,我设计的是主动抢单模式。配送服务在订单支付成功后被消息队列触发,创建配送任务。骑手端进入配送大厅,请求GET /api/delivery/tasks?status=WAITING_RIDER,拿到当前待接单的配送任务列表。每个任务卡片上显示商家地址、用户收餐地址、预计配送费、配送距离。

骑手点击接单时调POST /api/delivery/task/{taskId}/take,配送服务会做并发控制——这个任务只允许一个骑手接单。我用Redis的setnx指令实现了分布式锁,锁的key就是任务ID,接单成功后立即删除锁。如果没有这个锁,两个骑手同时点击接单,都可能请求成功,导致一单两送。用Redis分布式锁在这个场景里效果很明显,而且是标准的做法,不是土办法。

配送过程中的位置轨迹上报,我采用了一个比较轻量的方案。骑手端每10秒调一次POST /api/delivery/reportLocation接口,上报告当前经纬度和订单ID。配送服务把最近的位置写入Redis的delivery:location:{orderId},保留最近20条记录。小程序端用户查看配送进度时,调用GET /api/delivery/order/{orderId}/location,从Redis里查出最近的几个点,前端在地图上标出。这个方案不用WebSocket,也不依赖高德图SDK的位置上报,虽然实时性比那种"官方配送App"会差一点,但对于校园这种1公里范围内的配送场景足够用了。

这里有一个细节需要注意:位置上报接口的请求频率高,如果每次都写MySQL,数据库压力很大。我的做法是只把最新一条位置信息写入MySQL的delivery_track表,其他历史轨迹缓存在Redis里,保留时间设1小时,超过后自动失效。因为用户只看当次订单的实时配送进度,历史轨迹没有长期保留价值。

4.4 订单超时与库存扣减:分布式事务怎么处理

分布式环境下最容易出问题的,就是"多个服务的数据要同时更新成功"这个需求。这个项目里有两个典型场景:

第一个是创建订单时库存扣减和订单创建的一致性。如果订单创建了但库存扣减失败,会导致超卖;如果库存扣减了但订单创建失败,会导致库存凭空减少。我的处理方案是:订单服务创建订单记录(状态为待支付)后,通过OpenFeign调用商家服务的扣库存接口。如果扣库存成功,继续走支付流程;如果扣库存失败,订单服务立即把订单标记为CANCELLED并返回错误信息。这样虽然不能保证两个操作"同时成功",但能保证业务结果正确——要么订单创建且库存扣减,要么订单取消且库存不变。

这里用的是"补偿事务"的思想:先做主要操作,如果附带操作失败,通过置状态的方式把已做的操作回滚。不要在这个场景里硬上Seata分布式事务框架,因为扣库存接口是在线同步调用,失败率本身很低,用框架引入的成本比收益大。

第二个是高并发下单时的超卖问题。我在扣库存SQL上做了条件限制:UPDATE dish_stock SET stock = stock - #{quantity} WHERE dish_id = #{dishId} AND stock >= #{quantity}。这个SQL利用MySQL的行锁保证并发环境下的原子性——同一时刻只有一个请求能更新成功,其他请求因为条件不满足而更新0行。配合受影响行数的判断,扣减失败的请求直接返回"库存不足",简单高效。这个方案比先查库存再扣减要安全得多,也是业内电商系统的主流做法。

5. 高可用设计:缓存、限流与容灾

5.1 Redis在微服务中的缓存策略

微服务架构里的缓存策略,如果没有整体规划,很容易出现三个服务各自访问同一份数据然后缓存不一致的情况。这个项目里我理清了缓存的使用边界。

热点数据的缓存场景:店铺列表和店铺详情。校园外卖的特点就是饭点集中,每到中午11点到12点,大量用户同时打开小程序,商家列表接口的QPS会突然暴涨。我用Redis缓存店铺列表,key是缓存时间5分钟。店铺详情和菜品列表,因为数据变更频率低(商家一般一天改一次菜品),缓存时间设15分钟。菜品售罄状态不缓存,实时查数据库,因为售罄是动态变化的,缓存了反而误事。

库存缓存要不要做?这里我做了个取舍:库存不直接缓存在Redis里做预扣减,而是直接操作数据库。原因很简单,校园外卖商家的库存量级小,并发扣减频率没有那么极端,数据量也远远没到MySQL单表千万级的程度。在这个业务量下,直接操作数据库反而更可靠,避免Redis和MySQL数据不一致的问题。

5.2 Sentinel限流规则怎么配

网关层是整个系统的流量入口,我在Spring Cloud Gateway上集成了Sentinel,对关键接口配置了流控规则。

具体来说,主要针对下单接口POST /api/order和首页店铺列表接口GET /api/shop/list。单机QPS阈值分别设置为100和300,超出阈值的请求直接返回"系统繁忙,请稍后再试"。这里的阈值是结合校园用户规模估算的,假设学校2万人,高峰期同时在线点餐2000人,下单接口单机100的QPS,部署两个实例就是200,留出50%的冗余,差不多够用。

除了QPS限流,网关还要防刷单。在网关的GlobalFilter里做了一个简单的IP维度的频控:同一个IP在1分钟内调用下单接口超过5次,直接拒绝。虽然校园场景下同一WiFi出口IP都是同一个,但这个校验还是能拦截掉绝大多数脚本刷单行为。

5.3 服务容灾:熔断降级与兜底逻辑

微服务调用链中,任何一个下游服务挂了,都会对上游产生连锁影响。比如用户下单时,如果商家服务当时不可用,订单服务的OpenFeign调用会一直超时,积累大量等待线程,最终把订单服务自身的线程池耗尽。这就需要用Sentinel做熔断降级。

我给关键调用链路都加了降级规则。拿订单服务调用商家服务的"扣减库存"接口举例:设置最大调用时长为3秒,如果3秒内没有响应就判定为调用失败,走降级逻辑。降级逻辑里不直接抛异常,而是把订单标记为"支付待确认",同时发出告警。等商家服务恢复后再做库存核对和补偿处理。这样用户体验上是支付成功等待确认,而不是直接报错订单失败。

实际做项目时很多人忽略的一点是超时时间一定要设置。OpenFeign默认连接超时10秒、读超时60秒,这在微服务里太长了。如果下游服务真的挂了,一个请求等60秒超时,上游线程池迅速被打满,整个服务就雪崩了。我建议所有服务间的OpenFeign超时时间统一设置成连接2秒、读超时3秒,宁可请求失败走降级,也不能让超时拖垮整个系统。

6. 常见问题与排查技巧实录

6.1 服务间调用出现循环依赖

微服务拆分后最容易遇到的经典问题,就是两个服务互相调用形成循环依赖。这个项目里我就遇到过:订单服务需要调用用户服务查用户配送地址信息,同时用户服务在初始化时需要调用订单服务查历史订单数量。两边互相依赖,启动时Nacos注册中心里两个服务都显示注册成功,但实际调用时接口互相等对方数据,直接超时。

解决办法是梳理调用链,把其中一条调用换成异步消息,或者把公共数据抽出来。我的处理是把"用户服务查询历史订单数量"这个需求改为订单服务在用户下单时主动发送消息给用户服务去更新统计数据,而不是用户服务每次去查订单服务。这样调用链就变成单向的了:用户服务 → 订单服务,订单服务 → 消息队列 → 用户服务,不再有同步循环。

6.2 Nacos配置变更不生效

开发中遇到的另一个问题是修改了Nacos里的配置,比如数据库连接池大小、订单超时时间,但服务不生效。排查后发现原来需要在配置类上加上@RefreshScope注解,配置中心推送后Spring容器才能重新加载配置。另外要注意,Nacos配置文件的dataId必须和服务的spring.application.name严格一致,后缀写成-dev.yaml对应开发环境,如果用默认的application.yaml,多个服务拉到同一个配置,直接互相覆盖。

6.3 小程序真机预览时接口请求失败

这个坑出现得非常多。本地开发工具里接口都能通,但手机预览时全部请求失败。排查步骤按顺序来:

第一,确认手机和开发机在同一个局域网,开发模式下的请求地址不能是localhost,必须是开发机的局域网IP。

第二,如果接口地址是HTTP而不是HTTPS,在小程序后台"开发管理-开发设置"里配置服务域名,而且微信要求正式环境必须HTTPS,本地开发可以用"不校验合法域名"来绕过,但真机预览时这个选项是失效的,需要在预览页面勾选。

第三,检查网关的CORS配置是否放行了小程序端的请求。小程序请求不同于浏览器请求,没有预检请求,但如果网关设置了基于浏览器的CORS拦截,也可能影响。

6.4 骑手接单时分布式锁失效怎么办

前面提到用Redis的setnx做分布式锁,但在高并发抢单时发现偶尔还是会出现两个骑手都接单成功的情况。排查下来发现是锁的过期时间设置太短,接单逻辑还没执行完,锁就自动过期释放了,另一个骑手趁虚而入获取了锁。

解决办法是把锁的过期时间设为10秒,同时接单逻辑里加了SETEX和SETNX的原子性改进——用Redis官方推荐的SET key value EX 10 NX命令,一步完成设置值和过期时间。如果接单逻辑耗时较长,还可以用一个后台线程做锁续期,不过校园场景10秒足够,没必要过度设计。

6.5 热词搜索与接口设计的对应关系

顺着这次项目里搜索到的热词,最后再串一下各个高频需求点在这个项目中的落点,方便你对照自己项目查漏补缺:

热词本项目落点
springcloud入门简洁网关 + Nacos + Feign三个组件跑通即可,不必贪多
vue安装及环境配置Node 16+、Vue CLI 5,npm config set registry换国内镜像
小程序页面列表加载更多订单列表、配送大厅列表均实现分页下拉加载
微信小程序顶部导航栏高度自定义导航栏时用wx.getMenuButtonBoundingClientRect()取顶部胶囊位置
springboot配置多环境配置通过Nacos管理,本地开发用application-local.yml
小程序抓包Charles代理 + 微信开发者工具"不校验域名"组合
vue路由动态路由配合角色权限控制
springboot整合flink订单统计报表后续扩展可以走这个方向,initial版本直接查MySQL

7. 项目复盘与可扩展方向

项目完整跑通之后,我最大的感受是:微服务架构真正的难点不在写代码,而在约束和规范。服务一多,接口文档、异常码定义、日志打点、参数校验规则这些"看不见的东西"反而决定项目能不能顺利推进。我在这套系统中把所有服务之间的接口约定都收到了一个单独的api-docs模块里,定义好统一响应体Result<T>、统一异常码枚举、分页请求参数格式,每个服务的Feign接口都引用这个公共模块。这样三个服务并行开发时,没人会因为响应格式不统一而对字段。

后续可以扩展的方向,我列几个自己觉得高性价比的:

一是引入分布式事务框架Seata,用于支付和订单创建这种强一致场景,替代一直以来的手动补偿方案,更适合对数据一致性要求更高的商用版本。

二是做骑手位置实时推送WebSocket,代替现在的轮询上报方案,用户端体验会好很多,但需要额外处理连接状态的容灾。

三是把订单统计报表做成一个数据异步处理链路——订单服务发消息,统计服务消费消息,写入ES和报表中间表,报表查询从ES里出。这套路不算复杂,但很实用,是很多毕业设计或者项目升级都会走的路径。

四是接入高德地图的路径规划和配送范围圈选,商家端在地图上维护1.5公里配送范围,超出范围自动提示用户超出配送距离。这个功能对校园外卖来说很加分,但前期版本我用的是经纬度直线距离判断,够用就好。

最后说一个很多人容易忽略的点:做完整项目,最重要的是把日志链路追踪ID带上。我现在每个服务在启动时生成一个traceId,在网关生成并传递到后续所有调用链的服务,遇到问题在日志平台上直接按traceId把整个请求的日志串起来,排查效率提升几个档次。这个点建议所有做微服务的同学都提前接上,不要等技术债积累到排查报错找不到问题时再回头补。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询