又是一个外卖系统。每到毕业季,总会有一批又一批的小组把选题定在这里,然后在答辩前一个月开始问我说,学长,外卖系统怎么做才能不被老师挑毛病?
说实话,用 PHP 做外卖系统这件事,网上教程一抓一大把,但大多数都是这种路子:用户下单、商家接单、骑手配送,三个角色加一个后台,CRUD 一拼,完事。这样的系统答辩的时候确实能"跑起来",但老师只要问一句"订单状态是怎么流转的""并发下单会不会超卖""骑手多个人同时抢单怎么办",基本就卡住了。
这篇文章我想讲讲,从一个真实完成的小组项目出发,怎么把一个 PHP 外卖系统从"能跑"做到"能讲"。技术栈用的是 ThinkPHP 3.2.3,没错,就是这个老框架。你可能会觉得它旧,但毕设场景下它资料多、上手快、出活稳定,而且核心设计思路——订单状态机、数据库表结构、角色权限、实时性方案——放到任何框架里都是通用的。项目涵盖了用户端小程序/H5、骑手端、后台管理系统三个终端,走的是 B2C 订餐平台的完整闭环。我把中间踩过的坑、总结的经验、答辩前补的知识点全部理一遍,给正在做类似题目的同学一个可以直接参考的路线图。
1. 选型期先做的事:把订单流转图画清楚
很多小组拿到"外卖系统"这个题目,第一反应是打开编辑器开始建表。千万别急。我在这个项目里最先做的是一张草图,一张画在纸上的订单状态流转图。这张图决定了后面所有的数据库设计、接口设计、代码结构,甚至决定了小组成员之间的分工方式。
1.1 外卖和电商的本质区别:状态流转是主战场
为什么我强调先画状态图?因为外卖系统和一个普通电商系统的差别,恰恰不在"下单"这个动作上,而在"下单之后"。
普通电商的状态很简单:生成订单、付款、发货、确认收货,中间几乎没有实时协作的需求。但外卖系统里,一个订单从产生到完成,要经过用户、商家、骑手三个角色的接力。用户下单之后,商家要确认接单;商家出餐之后,骑手要去取;骑手取到餐之后,要送到用户手里;用户可能中途取消,商家可能超时未接单,骑手可能配送超时,这个订单还得有兜底机制。这一连串的动作,全靠"状态"这个字段在驱动。
如果项目一开始没把状态流转设计清楚,后面百分之百会出问题——最常见的例子就是:用户取消了订单,但商家那边还在备餐;或者骑手已经取了餐,但用户端还显示"商家制作中"。
1.2 最终落地的订单状态定义
我们这个项目最终定义的订单状态是这样的:
| 状态值 | 含义 | 触发动作 |
|---|---|---|
| 0 | 待支付 | 用户提交订单后创建 |
| 1 | 待接单 | 支付成功后推送给商家 |
| 2 | 待取餐 | 商家接单并开始制作 |
| 3 | 配送中 | 骑手取餐后开始配送 |
| 4 | 已完成 | 骑手确认送达,用户订单完成 |
| -1 | 已取消 | 支付前用户取消,或超时未支付系统自动取消 |
| -2 | 退款中 | 支付后用户/商家取消,进入退款流程 |
| -3 | 退款完成 | 退款处理完毕 |
这个状态定义看起来不复杂,但里面有两个细节值得展开。
第一个细节是 -2 退款中 和 -3 退款完成。很多毕设项目会在"取消订单"这里偷懒,直接把状态改成已取消就完事。但实际上,支付后的取消往往涉及退款流程,而退款是需要时间处理的,所以必须有"退款中"这个中间状态。我们当时是用了一个 refund_status 字段配合订单主状态来处理的,订单状态进入已取消后,再根据退款状态判断是全额退款还是部分退款。这样能回答老师"取消订单的资金怎么处理"这个问题。
第二个细节是状态流转图中必须标清楚"哪些状态可以到哪些状态"。比如:
- 待支付 → 待接单(支付成功)
- 待支付 → 已取消(用户取消或超时)
- 待接单 → 待取餐(商家接单)
- 待接单 → 已取消(商家拒单,或用户申请取消且商家同意)
- 待取餐 → 配送中(骑手取餐)
- 配送中 → 已完成(骑手送达)
这些流转关系,在代码里就应该被限定死。我们是用一个状态流转映射表来做的,合法流转才能通过,非法流转直接抛异常。这个设计在答辩的时候非常加分,因为它是从"系统会不会出错"的角度去考虑的,而不是"功能有没有实现"。
1.3 先画图再写代码,到底省了什么
这个状态图画完之后,我们做了几件事:
第一,把所有相关的数据库操作列出来。每个状态变化背后,需要更新哪些字段、记录哪些日志、通知哪个角色,全部列出来。第二,把所有页面和接口按状态变化来划分。用户端的订单列表、商家端的订单操作、骑手端的接单按钮,本质上都是状态迁移的触发点。第三,把一些超时的逻辑提前想好。比如支付超时、商家接单超时,用什么机制去触发状态变化。
这一套流程走下来,后面的编码其实就变成了"翻译"的过程。我见过太多小组写到一半推翻重来的,绝大多数原因都是没想清楚状态流转就动手,结果写到后面发现有业务流程串不上。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据库设计实战:六张核心表怎么建才经得起追问
数据库设计是答辩时老师一定会追问的地方,也是最容易暴露"你是真做过还是照着网上代码拼的"的地方。我先把我们最终敲定的核心表结构给你拆开讲,再说几个被追问过、也设计对了的细节。
2.1 核心表一览:不只是"用户+商品+订单"三件套
很多教程里的外卖系统只有三张表:用户表、商品表、订单表。我们实际做的时候扩展到了十几张表,其中起决定性作用的是下面六张:
用户表 user
sql复制CREATE TABLE `user` (
`user_id` int(11) NOT NULL AUTO_INCREMENT,
`openid` varchar(64) DEFAULT NULL COMMENT '小程序openid',
`username` varchar(50) NOT NULL,
`password` varchar(255) NOT NULL COMMENT 'password_hash',
`phone` varchar(20) DEFAULT NULL,
`avatar` varchar(255) DEFAULT NULL,
`status` tinyint(1) NOT NULL DEFAULT '1',
`create_time` int(11) NOT NULL,
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
商家表 shop
sql复制CREATE TABLE `shop` (
