爱跑腿模式外卖小程序源码:Java后端+微信前端完整工程
简介:提供一套可直接运行的爱跑腿风格外卖服务微信小程序源码,后端基于Java开发,前端严格遵循微信小程序规范。包含首页、店铺列表、商品详情页、下单全流程(含地址选择页)、订单列表与详情页、个人中心等全部核心功能页面,每个页面均配备WXML结构、WXSS样式和JS逻辑文件。项目内置weui.wxss作为基础UI样式库,utils目录封装常用工具函数,config.js统一管理API地址等全局配置,images目录存放界面截图(如shop.png、mine.png、order-list.png、select-address.png等)及小程序二维码图片gh_d4b22dcbcbad_430.jpg。附带标准LICENSE开源协议、详细README.md说明文档和.gitignore版本控制配置,支持本地调试与快速部署,适合用于二次开发、教学演示或项目原型搭建。
1. 项目概述:这不是一个“拿来就能上线”的Demo,而是一套有血有肉的外卖服务骨架
你搜到“爱跑腿模式外卖小程序源码”,点开下载链接,看到一堆文件夹和几十个JS、WXML文件,第一反应可能是:“哇,功能全齐了,改改logo就能上线?”——我试过三次,每次都在第三天凌晨两点对着控制台报错发呆。这套源码的真实定位,不是开箱即用的SaaS产品,而是一具被完整解剖、标注清晰、连神经末梢都保留着活性的“外卖服务人体标本”。它不教你如何注册微信小程序账号、不替你申请支付接口、也不帮你搞定Java后端服务器的Nginx反向代理配置;但它把从用户点击首页轮播图开始,到最终在订单详情页看到“已由骑手接单”这行字为止,中间所有关键节点的代码逻辑、数据流向、状态切换,全都摊开在你面前,连注释里都写着“此处需对接真实配送调度系统”。
核心关键词“外卖小程序”“Java后端”“爱跑腿源码”“微信小程序源码”,指向的不是一个技术名词堆砌,而是一个典型的本地生活服务闭环:用户端(微信小程序)→ 业务中台(Java Spring Boot 后端)→ 第三方能力(微信支付、地图定位、消息推送)。它解决的不是“有没有”的问题,而是“怎么组织、怎么衔接、怎么防错”的工程实践问题。比如,为什么下单流程里地址选择页(select-address.png)要单独成页,而不是嵌入商品详情页?因为真实场景中,用户可能反复切换地址、编辑收货人、甚至临时取消下单——这个页面必须能独立承载完整的地址CRUD操作,且状态不能污染商品浏览上下文。再比如,utils目录下那个dateUtils.js,表面看只是格式化时间,但你细看它的formatTimeAgo()方法,会发现它对“30分钟前”“2小时前”“昨天”做了分段处理,这是为订单列表页的“时间感知”体验埋下的伏笔,不是炫技,是用户心理预期管理。
这套源码最适合三类人:一是刚学完Spring Boot和微信小程序开发,想把零散知识点串成一条线的初学者;二是接到一个社区团购或校园跑腿小项目,需要快速搭出MVP原型的创业者;三是技术负责人,需要评估外包团队交付质量,或给新人安排一份“看得见、摸得着、改得了”的学习材料。它不适合指望复制粘贴就月入十万的纯小白,也不适合正在做千万级并发外卖平台的架构师——前者会卡在mvn clean install第一步,后者会觉得数据库设计太单薄。它卡在一个非常务实的位置:让你亲手拧紧每一颗螺丝,看清整台发动机是怎么转起来的。
2. 整体架构与设计思路:为什么选Java+微信小程序?这不是情怀,是权衡
2.1 技术栈选型背后的现实考量
看到“Java后端”,很多人第一反应是“重”“慢”“过时”。但当你真正去跑通一个外卖订单的完整生命周期,就会明白这个选择有多清醒。一个典型订单流涉及:用户提交订单 → 库存扣减 → 支付状态监听 → 骑手派单 → 配送状态更新 → 订单完成结算 → 退款逆向处理。这里面每一个环节,都要求事务强一致性、状态可追溯、错误可回滚。Java生态的Spring Boot + MyBatis Plus组合,在这方面是经过十年以上生产环境锤炼的“老司机”。它不像Node.js那样靠异步I/O拼吞吐,而是用成熟的事务管理器(@Transactional)、连接池(HikariCP)、以及完善的日志追踪(SLF4J + Logback),把“钱没收到但库存扣了”“骑手接单了但用户没收到通知”这类致命问题,压缩到可控范围内。
前端坚持用微信小程序,更是直击痛点。爱跑腿这类服务,核心用户就是拿着手机扫街边小店二维码的普通人。他们不会为了点个奶茶去应用商店下载一个App,但绝对愿意点开微信里一个轻量的小程序。微信小程序自带的用户体系(wx.login)、支付能力(wx.requestPayment)、地理位置(wx.getLocation)、消息模板(wx.requestSubscribeMessage),直接省去了你自建登录系统、对接银联/支付宝、采购高德/百度地图API的巨额成本和合规风险。源码里app.js里那几行wx.login()回调处理,看着简单,背后是微信开放平台为你扛下了OAuth2.0授权、token刷新、用户隐私合规等一整套复杂逻辑。
2.2 “爱跑腿模式”的本质:轻资产、重运营、快迭代
“爱跑腿”不是指某个具体品牌,而是一种业务模型:平台不自营仓库和骑手,只做信息撮合与规则制定。 源码里你看不到“自营仓库存管理”模块,店铺(shop)是作为独立实体存在的,每个店铺有自己的商品、库存、营业状态;骑手(rider)是作为第三方角色接入的,后端API里只有/api/rider/assign这样的派单接口,没有骑手考勤、薪资计算等重HR功能。这种设计,让整个系统变得极其轻盈。你可以在pages/shop/list.js里看到,店铺列表的加载逻辑,核心就两步:调用/api/shop/list获取数据,然后用wx:for渲染。没有复杂的多租户隔离,没有动态权限路由,因为初期你只需要服务好本地50家小店和20个兼职骑手。
这种轻量化,直接决定了源码的扩展路径。比如你想加“拼团”功能,不需要重构整个订单系统,只需在pages/goods/detail.js里增加一个“发起拼团”按钮,调用新的/api/group/buy接口;想加“会员等级”,就在pages/mine/index.js的用户信息区域,新增一个等级图标和权益说明,后端加个/api/user/vip接口返回等级数据即可。它像一块乐高底板,上面的积木你可以随时拆卸、替换、叠加,而底板本身的结构稳固性,已经由这套源码验证过了。
2.3 目录结构的工程哲学:分离关注点,降低修改成本
打开项目根目录,.gitignore和README.md的存在,说明作者是个有版本控制意识的开发者;LICENSE文件,则表明他尊重开源精神,也提醒你二次开发时注意协议约束(通常是MIT,允许商用但需保留版权声明)。而app、pages、utils、templates这些目录的划分,体现的是前端工程化的成熟度。
app目录是小程序的全局入口,app.js管理全局生命周期(onLaunch, onShow),app.json定义页面路由和窗口样式,app.wxss是全局样式覆盖层。这里不做业务逻辑,只做“定调子”的事。pages目录是真正的战场,每个子目录(如index、shop、order)就是一个独立页面单元,里面WXML、WXSS、JS三件套齐全。这种“页面即组件”的思想,让修改首页Banner和调整订单列表样式互不影响,改pages/index/index.js里的轮播图数据源,绝不会导致pages/order/list.js里的订单状态判断出错。utils目录是“工具人的聚集地”。request.js封装了统一的API请求方法,自动携带token、处理401跳转登录;storage.js对wx.setStorageSync做了Promise化包装,避免回调地狱;validate.js里那些isPhone()、isEmpty()校验函数,是你写表单时最常复用的“胶水”。我建议你第一次调试时,先花半小时把utils/request.js里的拦截器逻辑理清楚,它后面会救你无数次。templates目录容易被忽略,但它藏着提升开发效率的钥匙。比如order-item.wxml,定义了一个标准的订单卡片模板,pages/order/list.wxml和pages/order/show.wxml都通过<import>引入它,这样未来要改订单卡片样式,改一处,全站生效。
这种结构不是教科书上的理想模型,而是踩过坑之后的生存智慧。我见过太多项目,把所有JS逻辑塞进app.js,结果改一个分享功能,整个小程序闪退三次。
3. 核心模块解析与实操要点:从首页轮播到订单完成,每一步都藏着细节
3.1 首页(index):不只是展示,是流量入口的精密设计
首页看似简单,就一个轮播图(index.png)、几个分类图标、一个店铺列表。但它的性能和体验,直接决定用户是否划走。源码里pages/index/index.js的onLoad生命周期里,调用了两个关键API:/api/banner/list和/api/category/list。这里有个极易被忽视的细节:轮播图数据是预加载的。你去看app.js的onLaunch,会发现它在小程序启动时,就悄悄调用了/api/banner/list,把数据存在wx.setStorageSync('bannerData', res.data)里。这样当用户首次进入首页,pages/index/index.js的onLoad里直接wx.getStorageSync('bannerData')拿数据,页面几乎是秒开的。如果你删掉app.js里的预加载逻辑,首页白屏时间会从200ms拉长到1.2秒——在微信生态里,这就是生死线。
分类图标(category)的设计也暗藏玄机。pages/index/index.wxml里用的是<view wx:for="{{categories}}" wx:key="id">,但categories数组的来源,是/api/category/list接口返回的扁平化数据。真实业务中,分类可能有三级(美食→川菜→水煮鱼),但源码做了降维处理:后端返回时,就把“川菜”和“水煮鱼”都归到“美食”大类下,前端只渲染两级。这样既满足了用户快速查找的需求,又避免了前端做复杂的树形结构递归渲染,性能更稳。
店铺列表(shop-list)的滚动加载,用的是微信原生的onReachBottom事件。但源码里没用简单的page=1,2,3分页,而是用了游标分页(cursor-based pagination)。/api/shop/list接口的参数是lastId(上一页最后一个店铺的ID),而不是pageNum。这意味着,如果用户在刷列表时,后台新上架了一家店,传统分页可能会漏掉或重复,而游标分页能保证数据流的严格顺序。你在pages/index/index.js里能看到this.setData({ lastShopId: shops[shops.length - 1].id }),这就是游标在前端的落点。实操时,如果你要对接自己的店铺库,务必确保后端SQL里有WHERE id > #{lastId} ORDER BY id LIMIT 10这样的写法,否则分页会乱。
3.2 店铺与商品(shop/goods):状态驱动的交互逻辑
进入店铺页(shop.png),核心是商品列表和购物车。源码里pages/shop/detail.js的购物车逻辑,是理解“状态驱动UI”的绝佳案例。它没有用Vuex或Redux这类复杂状态管理,而是用小程序原生的data对象做状态容器:
data: {
cartItems: [], // 当前购物车商品数组
cartTotalPrice: 0, // 总价
cartItemCount: 0, // 商品件数
}
每次点击“+”添加商品,执行的是:
addToCart(goods) {
const cartItems = this.data.cartItems;
const existing = cartItems.find(item => item.id === goods.id);
if (existing) {
existing.count += 1;
} else {
cartItems.push({...goods, count: 1});
}
this.updateCart(cartItems); // 更新data并触发视图刷新
}
重点在updateCart()方法里,它不仅setData,还同步调用了this.calculateCart()重新计算总价和总件数。这种“数据变更 → 状态计算 → 视图更新”的链路,比直接在WXML里写{{cartItems.reduce(...)}}更可控,也更容易调试。我曾遇到一个Bug:用户连续快速点击“+”,购物车数量只加了1次。排查发现是setData是异步的,连续调用时this.data.cartItems还没更新,导致find找不到最新项。解决方案是在addToCart开头加个const currentCart = this.data.cartItems;,后续操作基于这个快照,而不是实时读取this.data。
商品详情页(pages/goods/detail.js)的规格选择,用的是微信原生的picker组件。但源码里picker的range属性,绑定的是this.data.specs,而specs数组是从/api/goods/specs?id=xxx接口动态获取的。这意味着,同一个商品,不同店铺可以配置不同的规格(比如A店卖“大杯/中杯”,B店卖“热/冰/少糖”),后端只要按规范返回JSON,前端无需改一行代码。这种“配置驱动”的思想,是支撑多店铺差异化运营的基础。
3.3 下单全流程(order/create):地址、支付、状态的三重奏
下单流程是整个小程序的心脏,源码把它拆成了三个紧密咬合的齿轮:地址选择(select-address.png)、订单确认、支付回调。
地址选择页(pages/address/select.js)的难点不在UI,而在数据一致性。用户在此页可以新增、编辑、删除地址,但这些操作必须实时同步到全局。源码的解法是:所有地址操作,都通过utils/storage.js的saveAddressList()方法,将最新地址数组存入wx.setStorageSync('addressList', list)。同时,在app.js的onShow里,监听wx.onAppShow事件,每次小程序切前台,都主动wx.getStorageSync('addressList')刷新一次。这样,无论用户是从首页跳转过来,还是从订单页返回,看到的都是最新地址列表。我建议你在调试时,在saveAddressList()里加一句console.log('地址已保存:', list),亲眼看着数据是如何流动的。
订单确认页(pages/order/create.js)的核心是防重复提交。用户点击“立即下单”按钮,源码里bindtap事件绑定了createOrder()方法,该方法第一行就是:
if (this.data.submitting) return;
this.setData({ submitting: true });
并在API调用成功或失败后,this.setData({ submitting: false })。这个submitting标志位,像一道闸门,确保用户狂点十次,后端也只收到一个订单创建请求。很多新手会忽略这点,结果测试时发现一个订单生成了五个,后台库存直接扣成负数。
支付环节,源码用的是微信官方的wx.requestPayment。但关键在于支付结果的最终确认,必须以后端回调为准。pages/order/create.js里wx.requestPayment的成功回调,只是告诉前端“微信支付弹窗已关闭”,不代表钱已到账。真正的支付成功,要等后端的/api/pay/notify接口被微信服务器调用,并返回SUCCESS。源码里/api/pay/notify的实现,会更新订单状态为PAID,并触发后续的库存扣减和骑手派单。所以,你在前端看到“支付成功”页面,其实只是“支付请求已发出”,最终状态要等pages/order/list.js里的定时轮询(setInterval调用/api/order/status?id=xxx)来确认。这个设计,是金融级安全的底线。
3.4 订单与个人中心(order/mine):状态机与用户体验的平衡
订单列表页(order-list.png)和详情页(order-show.png),是状态机(State Machine)思想的教科书范例。一个订单,从CREATED(已创建)→ PAID(已支付)→ ACCEPTED(骑手已接单)→ DELIVERED(已送达)→ COMPLETED(已完成),每个状态都有对应的UI样式、可操作按钮、文案提示。源码里pages/order/list.js的renderStatusText(status)方法,就是一个典型的状态映射表:
renderStatusText(status) {
const map = {
'CREATED': '待支付',
'PAID': '待接单',
'ACCEPTED': '配送中',
'DELIVERED': '已送达',
'COMPLETED': '已完成'
};
return map[status] || '未知';
}
但真正的难点在于状态变更的实时性。如果用户在订单列表页,骑手在后台系统里把订单状态从PAID改成ACCEPTED,前端如何立刻知道?源码的方案是:在pages/order/list.js的onShow里,启动一个setInterval,每15秒调用一次/api/order/list刷新数据。这个15秒,是权衡的结果——太短(如3秒)会压垮服务器,太长(如60秒)会让用户觉得“系统卡了”。我在实际部署时,把这个间隔根据订单量做了动态调整:高峰期(晚7-9点)设为10秒,低峰期设为30秒,用wx.getNetworkType()判断用户网络,4G/5G下用短间隔,WiFi下用长间隔。
个人中心页(mine.png)的“我的订单”入口,跳转逻辑很巧妙。它没有为每个订单状态(全部、待付款、待发货…)单独建页面,而是统一跳转到pages/order/list.js,通过URL参数?status=PAID来区分。pages/order/list.js的onLoad里,options.status拿到参数,动态设置this.data.currentStatus,再调用对应API。这样,新增一个“售后中”状态,前端只需在WXML里加一个<navigator url="/pages/order/list?status=AFTER_SALE">,后端加一个/api/order/list?status=AFTER_SALE接口,前后端改动都极小。这种“URL驱动状态”的设计,是应对业务快速变化的利器。
4. 实操过程与核心环节实现:从本地运行到部署上线的完整路径
4.1 本地开发环境搭建:避开那些“文档没写”的坑
拿到源码包,第一步不是急着npm install,而是检查Java后端的依赖。源码里后端项目应该是一个标准的Maven工程,pom.xml里会声明Spring Boot版本、MyBatis Plus、MySQL驱动等。但最容易卡住你的,是数据库初始化脚本。源码包里通常不会直接给你一个schema.sql,而是需要你运行后端的Application.java主类,让Spring Boot的spring.jpa.hibernate.ddl-auto=create自动建表。但这里有个巨坑:create模式会在每次启动时清空所有表!所以,你必须先把application.yml里的这一行注释掉:
# spring:
# jpa:
# hibernate:
# ddl-auto: create
然后手动执行建表语句。我一般会先启动一次,让它生成初始表结构,再用mysqldump导出SQL,保存为init_schema.sql,以后每次部署都用这个脚本。
前端微信小程序的本地调试,关键在config.js。这个文件里定义了API_BASE_URL,比如https://api.yourdomain.com。但本地调试时,你不可能让微信开发者工具直接访问你的线上域名(HTTPS限制)。解决方案是:在config.js里加一个环境判断:
const isDev = process.env.NODE_ENV === 'development';
export const API_BASE_URL = isDev
? 'http://localhost:8080/api'
: 'https://api.yourdomain.com/api';
然后在微信开发者工具里,勾选“不校验合法域名、web-view(业务域名)、TLS版本以及HTTPS证书”。记住,这只是开发阶段的权宜之计,上线前必须切回HTTPS域名并配置好SSL证书。
4.2 后端核心接口实现:以订单创建为例的深度剖析
我们以POST /api/order/create接口为例,看Java后端是如何把业务逻辑落地的。源码里这个接口应该在OrderController.java里:
@PostMapping("/create")
public Result<OrderVO> createOrder(@RequestBody OrderDTO dto,
@RequestHeader("Authorization") String token) {
// 1. 校验用户身份(从token解析出userId)
Long userId = jwtUtil.getUserId(token);
// 2. 校验地址有效性(查数据库)
Address address = addressService.getById(dto.getAddressId());
if (address == null || !address.getUserId().equals(userId)) {
throw new BusinessException("地址不存在或不属于当前用户");
}
// 3. 校验商品库存(关键!)
for (OrderItemDTO item : dto.getItems()) {
Goods goods = goodsService.getById(item.getGoodsId());
if (goods.getStock() < item.getCount()) {
throw new BusinessException("商品【" + goods.getName() + "】库存不足");
}
}
// 4. 创建订单(事务开始)
Order order = new Order();
order.setUserId(userId);
order.setAddressId(dto.getAddressId());
order.setStatus(OrderStatus.CREATED.getCode());
order.setCreateTime(new Date());
// 5. 扣减库存(在同一个事务里)
for (OrderItemDTO item : dto.getItems()) {
goodsService.reduceStock(item.getGoodsId(), item.getCount());
// ... 构建订单项
}
// 6. 保存订单(事务提交)
orderService.save(order);
return Result.success(new OrderVO(order));
}
这段代码的精髓,在于第4步和第5步被包裹在同一个@Transactional事务里。这意味着,如果扣减库存成功,但最后保存订单失败(比如数据库唯一索引冲突),整个事务会回滚,库存会自动恢复。这是保障数据一致性的铁律。很多新手会把“查库存”和“扣库存”分开写,中间插入其他逻辑,结果导致超卖。源码里goodsService.reduceStock()方法,应该是用UPDATE goods SET stock = stock - #{count} WHERE id = #{id} AND stock >= #{count}这样的SQL,利用数据库的原子性,避免并发扣减时的竞态条件。
4.3 微信支付对接:从签名到回调的全流程
微信支付是小程序里最易出错的环节。源码里支付相关逻辑,主要分布在三处:pages/order/create.js(前端调起支付)、PayController.java(后端统一下单)、PayNotifyController.java(支付结果回调)。
前端调起支付,核心是wx.requestPayment的参数。这些参数(timeStamp, nonceStr, package, signType, paySign)不是前端自己算的,而是必须由后端/api/pay/unifiedorder接口返回。源码里这个接口会做三件事:1)组装微信统一下单所需的XML参数;2)用商户API密钥对参数进行MD5或HMAC-SHA256签名;3)调用微信https://api.mch.weixin.qq.com/pay/unifiedorder接口,拿到prepay_id;4)再用prepay_id等参数,生成最终的paySign,返回给前端。
最关键的,是支付回调(Notify)。微信服务器会向你的/api/pay/notify发送一个POST请求,body是XML格式。源码里PayNotifyController.java的处理逻辑必须严谨:
@PostMapping(value = "/notify", produces = "text/xml;charset=UTF-8")
public String handleNotify(@RequestBody String xml) {
// 1. 解析XML,得到Map
Map<String, String> notifyMap = XMLUtil.doXMLParse(xml);
// 2. 验证签名(必须!防止伪造回调)
if (!WXPayUtil.isSignatureValid(notifyMap, apiKey)) {
return "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[签名失败]]></return_msg></xml>";
}
// 3. 检查支付结果
if ("SUCCESS".equals(notifyMap.get("result_code")) &&
"SUCCESS".equals(notifyMap.get("return_code"))) {
// 4. 更新订单状态(幂等性处理!)
String outTradeNo = notifyMap.get("out_trade_no"); // 商户订单号
Order order = orderService.getByOrderNo(outTradeNo);
if (order != null && order.getStatus().equals(OrderStatus.CREATED.getCode())) {
order.setStatus(OrderStatus.PAID.getCode());
order.setPayTime(new Date());
orderService.updateById(order);
// 5. 触发后续业务(如库存扣减、通知骑手)
afterPaySuccess(order);
}
}
// 6. 返回SUCCESS,告诉微信服务器“我收到了”
return "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
}
这里有两个魔鬼细节:一是isSignatureValid()必须用你微信商户平台的API密钥(apiKey)来验签,密钥错了,回调永远失败;二是afterPaySuccess()里的业务逻辑,必须是幂等的。因为微信回调可能因网络问题重试多次,你的代码必须保证,即使同一个outTradeNo的回调来了十次,订单状态也只会从CREATED变成PAID一次。
4.4 部署上线:Nginx、HTTPS与微信配置的终极 checklist
当本地调试一切顺利,下一步就是上线。这一步,90%的失败都源于配置疏漏。我整理了一份上线前必查清单:
-
后端服务器(Linux):
- Java环境:确认java -version输出的是JDK 8或11,不是JRE。
- MySQL:确认my.cnf里max_allowed_packet足够大(至少64M),避免大图片上传失败。
- Nginx反向代理:/etc/nginx/conf.d/yourdomain.conf里,必须配置:nginx location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }
注意proxy_pass结尾的/,少了它,路径会错乱。 -
HTTPS证书:
- 必须使用Let’s Encrypt的免费证书,用certbot一键部署。
- 在Nginx配置里,listen 443 ssl;下面,必须包含:nginx ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem; -
微信小程序后台配置:
- 服务器域名:在“开发管理”->“开发设置”里,把request合法域名填为https://yourdomain.com(注意是https,且不能带/api)。
- 业务域名:如果用了web-view,这里也要填。
- 支付配置:在“微信支付”->“开通微信支付”里,关联你的商户号,并在“支付授权目录”里填https://yourdomain.com/(注意结尾的/)。
- 消息模板:在“订阅消息”里,申请你需要的模板ID(如“订单支付成功”、“订单配送中”),并把ID填到后端代码里。 -
小程序代码上传:
- 在微信开发者工具里,点击“上传”,填写版本号(如1.0.0)和项目备注。
- 上传成功后,登录微信公众平台,在“版本管理”里,把刚上传的版本提交审核。审核通常需要1-3天。
上线后,第一个要验证的,不是功能,而是日志。在后端logback-spring.xml里,确保<root level="INFO">,并把日志输出到/var/log/yourapp/app.log。当用户反馈“下单没反应”,你第一时间要看的,就是这条日志里有没有OrderController.createOrder的ERROR记录。没有日志,等于在黑暗中修车。
5. 常见问题与排查技巧实录:那些让我熬夜改到凌晨的Bug
5.1 前端常见问题速查表
| 问题现象 | 可能原因 | 排查与解决 |
|---|---|---|
首页轮播图不显示,控制台报Cannot read property 'length' of undefined |
app.js里预加载的bannerData为空,或pages/index/index.js里没正确读取 |
1. 在app.js的onLaunch里,console.log('预加载轮播图:', res.data);2. 在pages/index/index.js的onLoad里,console.log('首页读取:', wx.getStorageSync('bannerData'));3. 确认/api/banner/list接口返回的数据结构是{code: 200, data: [...]},且data字段非空 |
| 点击“立即下单”无反应,控制台无报错 | pages/order/create.js里submitting标志位被意外置为true,且未重置 |
在createOrder()方法开头加console.log('提交状态:', this.data.submitting);检查是否有其他地方(如onUnload)错误地设置了this.setData({submitting: true}) |
| 商品详情页规格选择后,加入购物车数量总是1 | pages/goods/detail.js里addToCart()方法,没有把用户选择的规格ID(specId)传给后端 |
查看addToCart()调用的API参数,确认item.specId被正确赋值;后端OrderItemDTO实体类里,必须有specId字段并映射到数据库 |
| 订单列表页下拉刷新后,新订单不显示 | onPullDownRefresh里调用的API,没有传递正确的分页参数(如lastId) |
在onPullDownRefresh里,console.log('刷新时lastId:', this.data.lastShopId);确认API请求URL里包含了?lastId=xxx |
5.2 后端高频Bug与修复心得
Bug 1:支付回调一直收不到,微信商户平台显示“回调失败”
- 根本原因:Nginx配置里,location /api/pay/notify的路径,和后端Spring Boot的@PostMapping("/notify")不匹配。比如Nginx写了location /pay/notify,而后端是@PostMapping("/notify"),导致请求根本没转发到Java进程。
- 排查技巧:在服务器上执行sudo tail -f /var/log/nginx/access.log | grep notify,看是否有访问记录。如果没有,说明Nginx没转发;如果有,再看/var/log/nginx/error.log,看是否有502错误(后端没启动)或404错误(路径不对)。
- 修复方案:统一路径。推荐Nginx里写location /api/pay/notify,后端Controller里写@PostMapping("/pay/notify"),这样前后端路径完全一致,不易出错。
Bug 2:用户下单后,库存扣减了,但订单状态一直是CREATED,没变成PAID
- 根本原因:支付回调里,afterPaySuccess(order)方法抛出了未捕获的异常(如数据库连接超时),导致整个回调方法中断,return SUCCESS的XML没发出去。微信服务器收不到SUCCESS,就会不断重试,但每次重试都会因为同样的异常失败。
- 排查技巧:在PayNotifyController.handleNotify()方法最开头,加一行log.info("收到支付回调,原始XML: {}", xml);在afterPaySuccess()方法里,用try-catch包裹所有业务逻辑,并在catch里log.error("支付回调后续处理失败", e)。
- 修复方案:把afterPaySuccess()里的耗时操作(如调用骑手派单接口)改为异步,用@Async注解或消息队列(如RabbitMQ)解耦。回调方法本身只做最核心的订单状态更新,确保100ms内完成并返回SUCCESS。
Bug 3:小程序里调用/api/shop/list,返回401 Unauthorized
- 根本原因:utils/request.js里,header.Authorization的token,是wx.getStorageSync('token'),但用户从未登录过,token为空字符串,后端JWT解析失败。
- 排查技巧:在utils/request.js的interceptors.request.use里,console.log('请求头token:', config.header.Authorization);在后端JwtFilter里,log.info("解析token: {}", token)。
- 修复方案:在utils/request.js里,增加token有效性检查:javascript if (!token || token === 'undefined') { wx.navigateTo({ url: '/pages/login/login' }); return Promise.reject(new Error('未登录')); }
5.3 实操避坑经验:来自血泪教训的3条铁律
-
永远不要信任前端传来的任何数据
我曾把pages/order/create.js里用户选择的addressId直接当成数据库主键,传给后端。结果有用户用抓包工具,把addressId改成别人的ID,成功下单到他人地址。源码里后端createOrder()方法的第一行校验address.getUserId().equals(userId),就是为此而生。记住:前端是玻璃做的,后端才是铜墙铁壁。所有关键ID(用户ID、地址ID、商品ID),后端必须二次校验归属关系。 -
日志是你的第二双眼睛,不是装饰品
刚接手项目时,我习惯性地把所有log.info()都删掉,觉得“影响性能”。结果线上出问题,只能靠System.out.println重启看控制台。后来我学会了:在application.yml里,把logging.level.com.yourpackage=DEBUG,把关键方法的入参、出参、耗时,都打成log.debug()。线上用DEBUG级别,本地用INFO,用logback-spring.xml的<springProfile name="prod">做环境隔离。现在,我能在5分钟内,从10GB日志里,用grep "createOrder.*耗时"定位到慢查询。 -
“快速部署”不等于“不测试”,上线前必须跑通核心链路
源码里附带的README.md,往往只写了“运行mvn spring-boot:run即可”。但真正的上线前测试,必须模拟真实用户路径:
- 步骤1:用测试手机号,在小程序里完成注册、登录;
- 步骤2:进入一家店铺,选择一个商品,加入购物车;
- 步骤3:在地址选择页,新增一个地址并设为默认;
- 步骤4:下单,用微信沙箱环境支付(微信支付后台可配置);
- 步骤5:等待支付回调,确认订单状态变为PAID;
- 步骤6:在后台管理界面,手动把订单状态改为DELIVERED,看小程序订单详情页是否实时更新。
这六步,缺一不可。我见过太多项目,只测了“能打开首页”,就匆忙上线,结果用户第一单就卡在支付回调,损失的不仅是钱,更是信任。
最后再分享一个小技巧:源码里images目录下的那些截图(shop.png, mine.png等),别只当它们是UI参考。把它们一张张导入到微信开发者工具的“真机调试”里,用“截图对比”功能,逐像素检查你修改后的页面,是否和设计稿一致。有时候,一个2px的边框差异,就是用户投诉“页面丑”的全部原因。
简介:提供一套可直接运行的爱跑腿风格外卖服务微信小程序源码,后端基于Java开发,前端严格遵循微信小程序规范。包含首页、店铺列表、商品详情页、下单全流程(含地址选择页)、订单列表与详情页、个人中心等全部核心功能页面,每个页面均配备WXML结构、WXSS样式和JS逻辑文件。项目内置weui.wxss作为基础UI样式库,utils目录封装常用工具函数,config.js统一管理API地址等全局配置,images目录存放界面截图(如shop.png、mine.png、order-list.png、select-address.png等)及小程序二维码图片gh_d4b22dcbcbad_430.jpg。附带标准LICENSE开源协议、详细README.md说明文档和.gitignore版本控制配置,支持本地调试与快速部署,适合用于二次开发、教学演示或项目原型搭建。
更多推荐




所有评论(0)