简介:完整版微信外卖小程序系统源码与数据库,基于Java语言开发,面向需要搭建或二次开发在线订餐平台的开发者、中小商户及技术学习者。系统覆盖商家管理、骑手管理、用户端小程序、订单处理等完整闭环,支持微信支付、多场景适用,前后端齐全,稳定性和可扩展性较好。压缩包共2825个文件,以fr3报表模板、data/rls数据文件、log日志及PDF文档为主,辅以dll、exe、配置文件等,总大小27.61MB,目录结构清晰,便于按模块查阅。数据库涵盖用户、商家、商品、订单、骑手等核心数据表,外键关联保证数据一致性;源码提供了完整的API接口及权限体系,开发者可根据实际业务调整商家入驻、配送跟踪、支付对接等逻辑,也可作为课程设计或毕业设计的蓝本。目前已有908人学习下载,是快速理解和落地微信外卖业务的实用参考资料。
1. 微信外卖小程序系统:Java 后端与小程序前端都齐了,一条链路跑通下单到配送
接到餐饮商户的小程序外卖需求时,很多人的第一反应是从零开始写一套。我去年也干过这事,结果光是梳理“商家怎么改菜单、骑手怎么接单、订单怎么流转”就花了两天,更不用说微信支付回调、小程序域名校验这些细节。后来我换了个思路,直接拿一套 Java 开发、前后端齐全的微信外卖小程序系统源码做二次开发,数据库导进去、后端跑起来,再用微信开发者工具加载小程序端,当天晚上整个下单链路就通了。
这套资源解决的是外卖业务最实际的闭环问题:用户在微信里搜商家、下单支付,商家后台接单备餐,骑手取餐配送并更新状态,订单全程可跟踪。后端是 Java 写的,多商户并发时比脚本方案稳;小程序端、商家端、数据库脚本都带,不需要自己造表。资源标题里写的 JAVE 就是 Java 的笔误,实际开发语言是 Java,前后端分离,适合直接拿来改业务。
适合三类人:想快速上线外卖平台的商户,做餐饮、商超信息化项目的开发者,以及接小程序外包、需要靠谱模板起手的技术团队。新手照着部署流程一天能跑通,熟手可以研究模块拆分和表结构设计,把订单模型改成自己的业务。
2. 三端模块与订单流转:商家、骑手、用户小程序各管哪一段
2.1 用户端小程序:从搜索商家到支付完成的页面链路
用户端是所有流量的入口,它的页面链路一般是:首页商家列表 → 分类筛选 → 商家详情 → 商品列表(含规格选择) → 购物车 → 确认订单 → 支付 → 订单详情。这条链路每一环都对应后端接口,接口设计基本跟着页面走:/api/shop/list给首页用,/api/goods/list给商品列表用,/api/order/create给确认订单用,/api/order/pay给支付用。
这里有一个特别影响体验的点:商品列表的加载方式。很多初级开发习惯一次性把全量数据返回,商品少没事,上百条就开始卡。这套源码里用的是列表加载更多:用户滑动到底部,小程序端带着page和limit两个参数请求下一页,后端分页返回,前端把新数据追加到当前数组。
// 页面列表加载更多:滚动到底部时拉取下一页商品数据 let pageNo = 1; const pageSize = 10; Page({ data: { goodsList: [], hasMore: true }, loadGoods(shopId) { wx.request({ url: 'https://api.example.com/api/goods/list', data: { shopId: shopId, page: pageNo, limit: pageSize }, success: (res) => { const { code, data } = res.data; if (code === 0 && data.rows.length > 0) { this.setData({ goodsList: this.data.goodsList.concat(data.rows) }); pageNo += 1; } else { this.setData({ hasMore: false }); } } }); } });这段代码的逻辑是:每次请求带上当前页码page和每页条数limit,后端返回rows后,用concat把新数据追加到goodsList,而不是整体替换。hasMore用来标记是否还有下一页,列表触底时如果已经返回空数组,就把hasMore置为false,避免重复请求。这里最容易写错的是pageNo累加时机——必须在success里累加,不能放在请求发出前,否则失败重试时会跳过一页。
还有一个前端细节容易被忽略:微信小程序顶部导航栏高度在不同机型上不一样,iPhone 刘海屏和安卓状态栏高度差异很大。首页做吸顶搜索框时,如果写死一个像素值,部分机型会盖住胶囊按钮。我的习惯是用wx.getSystemInfo()拿到statusBarHeight,再动态计算导航栏高度,而不是写死。
2.2 商家管理端:菜单、库存、营业时间与订单处理
商家模块的权限模型是:商家账号登录后只能操作自己的店铺,后端靠shop_id字段做数据隔离。商家能做的事主要四类:上传菜单、设置价格、管理库存、调整营业时间。对应到表结构分别是商品表、店铺表和分类表,操作入口一般是独立的后台页面,不走用户端小程序。
商家端和用户端不应该混在一个页面里。用户端是逛和买,操作频率低、页面要轻;商家端是改数据,操作频率高、表单要全。我见过有人把商家功能塞进用户小程序里加个隐藏入口,结果审核和体验都很别扭。这套源码的做法是商家端单独一套页面,菜单管理用列表展示,修改商品直接弹表单保存;营业时间用一个开关加时间段选择,存成两个 time 字段,查询时直接判断当前时间是否在区间内。
商家端“接单”动作是整个流程的开关。用户支付成功后生成待接单订单,商家端订单列表里出现这笔订单,商家点击“接单”,订单状态从“已支付”变成“已接单”,之后骑手端才能看到这笔订单。这个状态更新后端一般是一个通用接口:
// 订单状态更新接口:校验状态转移合法性 @RequestMapping("/api/order/status") public Result updateOrderStatus(@RequestBody StatusUpdateDTO dto) { // 1. 查出当前订单,校验 operator 是否有权限操作该订单 Order order = orderMapper.selectById(dto.getOrderId()); if (order == null) { return Result.error("订单不存在"); } // 2. 状态机校验:只有 fromStatus 等于当前 status 才允许流转 boolean allowed = stateMachine.canTransit(order.getStatus(), dto.getTargetStatus(), dto.getOperatorType()); if (!allowed) { return Result.error("非法状态转移"); } // 3. 更新状态,并记录状态变更日志 orderMapper.updateStatus(dto.getOrderId(), dto.getTargetStatus()); orderLogMapper.insert(new OrderLog(order.getId(), order.getStatus(), dto.getTargetStatus())); return Result.ok(); }这段代码的核心是状态机校验。canTransit接收当前状态、目标状态和操作者类型,在允许列表里判断,比如商家只能把“已支付”改成“已接单”,不能把“已完成”改成“配送中”。校验放在 Service 层而不是 Controller 层,是为了防止小程序端绕过接口直接拼参数改状态。状态变更日志也要记录,后面排查问题全靠它。
2.3 骑手端:注册、登录、接单与配送状态更新
骑手端核心是“附近订单”和“配送状态”。骑手登录后能看到配送范围内的待配送订单,点击接单后订单绑定到该骑手,其他骑手不能再接。这个绑定在后端就是订单表里rider_id字段从空值变成当前骑手 id,同时状态从“已接单”变成“配送中”。
这里有一个并发问题值得注意:两个骑手同时点击接单怎么办?常见做法是后端用带条件的更新语句,比如update orders set rider_id = ? where order_id = ? and rider_id is null,这个where条件保证只有一个骑手能更新成功,另一个更新的影响行数是 0,接口返回“已被接单”。这个写法比“先查再改”更稳,二次开发时值得保留。
骑手更新配送状态一般用几个按钮:已取餐、配送中、已送达。每次点击都调同一个状态流转接口,后端根据当前状态决定能否跳到目标状态。这个设计比写三个独立接口好维护,新增状态只需要改配置,不用动前端按钮。
配送状态对用户端来说就是订单进度条的数据来源。用户端每隔几秒轮询订单详情接口,把最新状态显示出来。轮询间隔不要设太短,5 到 10 秒一次足够,高峰期请求量会很可观。如果后续订单量大,可以改成 WebSocket 推送,但这套源码的轮询方案在中小业务量下完全够用。
2.4 订单处理模块:从创建到完成的全流程自动化
把三个角色串起来看订单的完整生命周期:创建(待支付)→ 支付确认 → 商家接单 → 骑手接单 → 配送中 → 已送达/完成,中间可能进入取消、退款等异常状态。每一步状态转移都由后端校验和控制,不是靠人工在不同系统里重复录入,这就是“自动化处理,降低出错概率”的含义。
数据库订单状态表是整个系统的信息中枢:商家端看到的是同一个表,骑手端看到的也是同一个表,用户端进度条的数据还是同一个状态字段。状态转移规则建议直接放在枚举或配置表里,后期要改流程只改配置。下面这张表是各角色的职责和支撑数据:
| 角色 | 主要负责 | 关键操作 | 支撑数据表 |
|---|---|---|---|
| 用户 | 下单、支付、查看进度 | 创建订单、支付、订单详情 | user、orders |
| 商家 | 菜品维护、接单 | 接单、完成出餐 | shop、goods、orders |
| 骑手 | 配送 | 抢单、取餐、送达 | rider、orders |
| 管理员 | 平台运营 | 商家审核、骑手审核、数据统计 | admin、shop、rider |
还有一个防重复提交的细节:小程序网络差时,用户可能连续点两次支付按钮导致创建两笔订单。后端一般做法是创建订单前做幂等校验,同一个用户对同一家店、同一个购物车内容短时间内只能创建一笔有效订单,或者让前端生成一个临时订单号,后端根据这个号去重。这个机制在二次开发时千万不要省略,线上真出过这种问题。
3. 数据库设计拆解:五类核心表如何支撑订单全生命周期
3.1 核心表清单与外键关系
这套系统的数据量级属于中小型电商,表结构按用户、商家、商品、订单、骑手划分。主要表之间的关系很清晰:订单表同时关联用户、商家和骑手,商品表关联商家。通过外键保证了数据一致性,不会出现“订单属于一个不存在的商家”这种脏数据。
| 表名 | 核心字段 | 外键关联 | 作用 |
|---|---|---|---|
| user | id, nickname, openid, mobile | 无 | 用户登录和身份标识 |
| shop | id, name, address, business_hours, status | 无 | 商家入驻与营业状态 |
| goods | id, shop_id, name, price, stock, category_id | shop_id → shop.id | 菜单与库存 |
| orders | id, order_no, user_id, shop_id, rider_id, status, total_amount | user_id → user.id, shop_id → shop.id, rider_id → rider.id | 订单流转 |
| rider | id, name, mobile, status, current_lat, current_lng | 无 | 骑手接单与位置 |
用户表的openid字段是微信生态的独特标识,同一个用户在小程序里不管换什么设备,openid都不变,用它做用户唯一索引比用自增 id 更可靠。这是小程序开发里最容易忽略的一点。
订单表里要把对外展示的订单号和数据库主键分开。主键用自增 id,对外展示用order_no,生成规则一般是时间戳加随机数。这样用户不会通过单号猜到平台的订单量,也方便后续做订单异步对账。我看到很多二次开发把order_no直接当主键用,订单量大后写入性能会下降。
3.2 订单表的状态字段设计
订单表最核心的是status字段,它直接决定订单当前阶段,各端页面按status显示不同的按钮和文案。典型设计如下:
| status 值 | 含义 | 可操作角色 | 合法下一状态 |
|---|---|---|---|
| 0 | 待支付 | 用户 | 1(已支付)或 5(已取消) |
| 1 | 已支付 / 待商家接单 | 商家 | 2(已接单)或 6(退款) |
| 2 | 已接单 | 商家 / 骑手 | 3(配送中) |
| 3 | 配送中 | 骑手 | 4(已送达/完成) |
| 4 | 已完成 | - | - |
| 5 | 已取消 | - | - |
| 6 | 已退款 | - | - |
状态转移规则在代码里实现成状态机,不允许非法跳转。比如从“待支付”直接跳到“已完成”,业务上不可能,但如果状态校验不严格,调用接口的人就能破坏订单流程。不少改造项目后期出问题,都是因为新增了绕过状态机直接改数据库的脚本,结果三端显示不一致。
3.3 常用查询 SQL:统计订单量、查找附近骑手、分页列表
商家端最常见的需求是统计今日订单数和营业额。下面这条 SQL 直接查订单表聚合:
-- 统计指定商家的今日订单数和营业额 SELECT COUNT(*) AS order_count, COALESCE(SUM(total_amount), 0) AS total_amount FROM orders WHERE shop_id = 1 AND status = 4 AND pay_time >= CURDATE() AND pay_time < DATE_ADD(CURDATE(), INTERVAL 1 DAY);这里有两个容易被忽略的条件:status = 4表示只统计已完成的订单,退款的不计入营业额;pay_time限定当天范围,不加的话历史订单也会被统计进去。COALESCE是为了防止当天没有订单时SUM返回空值。
骑手端“抢单列表”的基础是附近空闲骑手查询。MySQL 5.7 及以上版本提供ST_Distance_Sphere球面距离函数,可以直接按经纬度算距离:
-- 查找当前坐标 3 公里内的空闲骑手,按距离排序 SELECT id, name, mobile, ROUND(ST_Distance_Sphere( POINT(current_lng, current_lat), POINT(116.397, 39.909) ) / 1000, 2) AS distance_km FROM rider WHERE status = 0 AND ST_Distance_Sphere( POINT(current_lng, current_lat), POINT(116.397, 39.909) ) <= 3000 ORDER BY distance_km ASC;ST_Distance_Sphere返回单位是米,/ 1000转成公里。经纬度数据类型要用decimal(10,6),精度不够时距离计算偏差很大。这个查询在骑手数量过万后性能会变差,因为没法走索引,后续可以引入空间索引或网格分桶,不过中小业务量直接跑没问题。
订单列表分页查询是商家端订单页的基础,按创建时间倒序分页取数据:
-- 按 shop_id 分页查询订单,按创建时间倒序 SELECT id, order_no, status, total_amount, created_at FROM orders WHERE shop_id = 1 ORDER BY created_at DESC LIMIT 10 OFFSET 0;LIMIT 10 OFFSET 0是第一页,LIMIT 10 OFFSET 10是第二页。OFFSET越大查询越慢,数据量过万后建议改用游标分页,用created_at <= 上一页最后一条记录的 created_at代替OFFSET,效率会好很多。数据库增删改查本身不难,难的是在数据量上来后仍然保持稳定。
3.4 索引设计与字符集注意事项
订单表的查询条件基本都带shop_id和status,建索引时建议建联合索引(shop_id, status, created_at),这样上面三条 SQL 都能走索引。如果单独给status建索引,效果很差,因为状态字段区分度太低,优化器很可能放弃索引做全表扫描。这也是数据库设计里最常被问到的问题之一。
字符集方面,数据库、表、连接串三层都要保持utf8mb4。微信用户昵称经常带 emoji 表情,utf8存不了四字节字符,只有utf8mb4能存。如果发现某个用户的昵称插入报错,或者导入数据后中文乱码,先检查字符集配置。连接串里加上characterEncoding=utf8mb4,导入 SQL 时命令行加--default-character-set=utf8mb4,才能保证全链路一致。
4. 从源码到可运行:本地部署与微信开发者工具接入
4.1 环境准备与项目结构
部署这套系统前先确认机器上有什么:JDK 8 或 11、Maven 3.x、MySQL 5.7 或 8.0、微信开发者工具稳定版,以及一个微信小程序 AppID,测试号也可以。如果本地没装 Maven,直接用 IDE 里集成的 Maven 也行,但命令行方式最通用。
项目目录一般分两块:后端是 Java Maven 工程,标准结构是src/main/java放代码、src/main/resources放配置,数据库脚本通常在sql目录;前端是微信小程序工程,页面在pages目录,公共方法在utils目录,全局配置在app.js和project.config.json。打开项目后先别急着跑,先找到sql目录里的 .sql 文件,这是建库脚本。
资源包里除了代码和脚本,还带了左侧图标.bmp、顶端图标.bmp、fpdfcjk.bin、license.dat,以及一组 .rls.data 和 .fr3.data 文件。这些是原工程遗留的资源文件,用途是图标显示、PDF 中文字体映射和报表模板,Java 后端跑基础下单流程不强制依赖,但部署时建议原样保留,后面做小票打印或报表导出时会用到,删了再找就麻烦。
4.2 数据库初始化:建库导入脚本
常见做法是先创建数据库,再导入 SQL 脚本。命令行操作如下:
mysql -uroot -p -e "CREATE DATABASE waimai DEFAULT CHARACTER SET utf8mb4;" mysql -uroot -p waimai < sql/waimai.sqlDEFAULT CHARACTER SET utf8mb4一定要写,不然建表默认 latin1,导入中文全是乱码。这是这条链路里最常见的翻车点。导入完成后可以用SHOW TABLES;检查表是否齐全,用SELECT COUNT(*) FROM user;确认数据至少能读。
4.3 配置修改:数据库连接、端口与微信参数
后端配置集中在src/main/resources/application.yml,重点看数据源和微信支付参数:
server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/waimai?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver wx: appid: wx1234567890abcdef secret: your_app_secret mch-id: 1500000000 pay-key: your_pay_key这是标准的 Spring Boot 数据源配置。url里的characterEncoding=utf8mb4保证读写中文不乱码,serverTimezone=Asia/Shanghai不加的话 MySQL 8 连接常报时区错误。微信相关参数换成自己申请小程序时拿到的真实 AppID、AppSecret,支付参数换成微信支付商户号对应的mch-id和 API 密钥。
提示:如果打开项目发现没有
sql目录,先检查database或docs目录,脚本一般在这几个位置。整个数据库的建表脚本和初始数据通常是一个文件,也可能按模块拆成多个文件,逐个按顺序导入即可。
4.4 启动后端与服务自测
后端启动方式很直接:
mvn clean package -DskipTests java -jar target/waimai-backend.jarmvn clean package是打包,-DskipTests跳过测试加快速度,java -jar启动可执行 jar。启动日志里看到Started Application in x seconds和Tomcat started on port 8080说明启动成功。启动后用 curl 验证接口:
curl http://localhost:8080/api/health如果返回 ok 或者 JSON 数据,说明后端和数据库已经连通。如果启动失败,先看日志里是不是数据库连不上,最常见原因是密码错误或时区配置缺失。端口被占用就把server.port改成 8081,但小程序端的baseUrl也要跟着改。
4.5 小程序端配置:AppID、合法域名与真机调试
小程序端用微信开发者工具打开前端工程目录,修改app.js或config.js里的接口地址指向本地后端,再把 AppID 换成自己的。开发者工具默认会校验 request 合法域名,本地开发时会把http://localhost拦截下来,这一步有两种处理方式。
开发阶段:在微信开发者工具的“详情 → 本地设置”里勾选“不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书”,这样本地 http 接口能正常调。上线阶段:必须把后端部署到公网 HTTPS 域名,在微信公众平台的小程序后台把域名加进 request 合法域名列表,然后关掉开发者工具的“不校验”开关,否则正式发布后用户端请求全部失败。
真机调试时,手机访问不到电脑的localhost,需要把接口地址改成电脑的局域网 IP,比如http://192.168.1.100:8080,同时保证手机和电脑在同一 WiFi 下。还有一种先官方再本地的省心做法:后端已经部署到公网时,真机直接连公网地址调试,跳过了局域网这一步。
5. 避坑指南:部署与二次开发中的 5 个常见问题
5.1 小程序请求后端失败,开发者工具正常真机全挂
现象:开发者工具里接口一切正常,一用真机预览,页面数据全空白,请求全部失败。
原因:开发者工具勾选了“不校验合法域名”,真机环境不认这个开关;同时接口地址写的是localhost或 127.0.0.1,手机访问不到电脑本机。
解决:开发阶段把后端接口地址改成电脑的局域网 IP,手机和电脑连同一个 WiFi 再试;上线前把后端部署到公网 HTTPS 地址,在微信公众平台配置好 request 合法域名。我一般上线前强制走一遍真机调试,不能只在开发者工具里点“预览”就完事。
5.2 微信支付回调不触发,订单一直停在待支付
现象:用户微信里已经扣款成功,但订单状态没变,一直显示“待支付”,商家端接不到单。
原因:微信支付回调 URL 必须是公网可访问的地址,本地 localhost 收不到微信服务器的通知;另一种情况是回调地址配置和实际控制器路径不一致。
解决:开发时将本地服务映射出一个临时公网地址,让微信回调能打进来。这类工具按“本地公网地址映射”搜索就有现成的,选稳定一点的即可。同时在后端回调接口打印日志,看微信服务器是否真的请求到了本地。排查时也可以用抓包工具观察回调请求是否到达。
5.3 导入 SQL 后中文乱码,商品名全变成问号
现象:数据库里的中文显示为乱码或问号,小程序端商品名、商家名全都不可读。
原因:建库时没指定utf8mb4,MySQL 默认字符集不支持中文存储;或者导入脚本时命令行没带字符集参数。
解决:建库用CREATE DATABASE ... DEFAULT CHARACTER SET utf8mb4,导入时加--default-character-set=utf8mb4。已经乱码的库别直接改字符集,大概率改不干净,最省事的是把数据导出再重建库导入。从那以后我每次建库都会先查一遍字符集,而不是等数据进去再返工。
5.4 加载更多列表重复数据,翻页时同一批商品反复出现
现象:用户滑动到底部触发加载更多,商家列表或商品列表和前面内容重复,或者翻页后出现大段空白。
原因:pageNo没有在状态里正确记录,下一页请求时又把第一页数据返回;前端追加数据时用了整体赋值,把原有列表替换成了新的一页。
解决:前端用this.data.page记录当前页码,每次请求成功后page + 1再更新;setData时用concat追加而不是直接赋值。后端确认分页参数是page + limit还是pageNo + pageSize,两边的字段名必须对齐,这是联调最容易错的地方。
5.5 资源包里的 license.dat 和报表文件能删吗
现象:把资源包里的license.dat、AAAemployee.rls.data、test.fr3.data等文件删掉后,启动或用到报表导出功能时,报授权缺失或文件无效的错误。
原因:这些文件不是垃圾文件。fpdfcjk.bin是 PDF 中文字体映射,两个.bmp是界面图标,license.dat是授权文件,.rls.data和.fr3.data是原工程的报表模板数据。Java 后端跑基础下单流程不一定用得上它们,但扩展功能,比如打印小票、导出对账单,会按路径读取这些文件,删了程序找不到资源就报错。
解决:部署时原样保留整个资源目录,不要改名、不要挪位置。自己重新打包时也记得把这些文件复制过去。最稳妥的做法是目录结构和源码包保持完全一致,只改配置,不动资源。
注意:如果你二次开发后准备把系统交付给客户,
license.dat涉及原工程的授权机制,商用前建议确认授权范围,避免交付后出现授权争议。
6. 二次开发进阶:订单状态机扩展与多场景改造
订单状态机是这套源码里最值得改造的部分。看懂状态机,就能把外卖系统扩展成商超配送、生鲜电商、自提点模式。先看这个典型的状态枚举:
public enum OrderStatus { CREATED(0, "待支付"), PAID(1, "已支付"), ACCEPTED(2, "已接单"), DELIVERING(3, "配送中"), COMPLETED(4, "已完成"), CANCELED(5, "已取消"), REFUNDED(6, "已退款"); public final int code; public final String desc; OrderStatus(int code, String desc) { this.code = code; this.desc = desc; } }枚举的每个值对应数据库status字段的数字。新增状态时,比如加一个“商家拒单”,需要在枚举类、数据库字典表、状态机校验逻辑三处同步修改,缺一处就会状态不同步。我看这套源码的习惯是先画一张状态转移表,把每个旧状态合法跳转到的新状态列出来,然后在代码里用允许列表校验,而不是堆一堆 if-else。
多场景扩展有个取巧的设计:订单表加一个delivery_type字段,0 表示外卖配送、1 表示到店自提、2 表示堂食。自提和堂食订单不分配骑手,用户端进度条显示“到店取餐”而不是骑手位置;商家端接单后直接标记“备餐完成”,用户凭取餐码到店取餐。这个改法只动了前端渲染和后端查询过滤逻辑,订单状态机完全不用动。
还有一种常见扩展是商超外卖:商品表加spec规格字段,比如“500ml/瓶”“1L/桶”,购物车按规格维度计算。用户端选规格时从商品详情的规格列表里选,其实就是把原来的单一价格改成多规格价格组。数据库层面加一张goods_sku表,订单明细表关联sku_id而不是goods_id,价格快照存在订单明细里,这样商品改价不影响历史订单。
我第一次跑这套系统时,被“商家接单后骑手才能看见订单”这条规则卡了半天,后来发现后端查询接口里用status做了过滤,不是接单动作去通知骑手端。从那以后,我每次改订单流程都强制先画一遍状态转移表,再动代码,这套习惯帮我少踩了不少坑。希望这个做法对你也同样有用。
本文还有配套的精品资源,点击获取