简介:PHP外卖点餐源码是一套面向在线餐饮行业的完整网站应用解决方案,适合PHP初中级开发者、自由职业者及中小餐饮商户用于快速搭建或定制外卖订餐平台。源码基于PHP语言开发,覆盖用户注册登录、菜单浏览、购物车、下单支付、订单追踪等完整业务流程,同时体现响应式布局、MySQL数据库管理、第三方支付接口对接、服务端业务逻辑与常见安全防护等核心技术,界面美观且功能结构清晰。压缩包共包含1166个文件,约5.13MB,其中以193个php逻辑文件、83个js交互脚本、19个css样式、53个html页面及大量jpg/gif图片素材为主体,辅以db数据库、htm说明文档和系统流程图;图片素材丰富,可直接用于菜品展示与界面设计。目前该资源已有1814人学习下载。开发者可依据源码说明、安装指南、使用必读与更新日志快速部署,结合授权文件了解使用约束;既能借此进行PHP全栈实战训练,也能在现有框架上继续扩展多商户、优惠券、订单管理等功能,是入门外卖系统二次开发的不错起点。
1. 从「看不上」到「自建外卖系统」:这份源码的真实价值
见过太多想做个外卖点餐网站的人,第一反应是去定制外包,开口就是几千,等代码拿回来一看,前后端耦合、没有注释、数据库字段全靠猜,改一个功能恨不得重写半个项目。这份 PHP 外卖点餐源码不一样,它是那种「你在本地一跑就能看到完整业务流」的完整工程,用户端、商户端、配送端三套入口齐全,界面干净,核心功能清晰,适合三种人:想快速交付一个可演示项目的开发者,需要一套代码研究订单流程的学生,以及想低成本启盘私域外卖的个体老板。它也确实是 PHP 里很少见的「不劝退源码」——前提是你能把部署环境填平,这正是接下来要讲的。
2. 跑通本地环境:PHP 版本选错,后面全是玄学坑
2.1 环境选型:为什么我坚持用 PHP 5.6 兼容模式跑旧源码
拿到源码第一件事不是看代码,而是看根目录下有没有composer.json。这份资源用的是 PHP 原生开发,没有引入重量级框架,依赖主要在include和class目录里。常见做法是本地装一个集成环境,我一般用 PHPStudy,因为它可以一键切换 PHP 版本。
这里有个关键参数:很多老 PHP 项目在 PHP 7.0 以上会直接抛mcrypt相关报错,或者each()函数被移除导致白屏。你拿到这份源码后,先确认编码风格——如果大量使用mysql_*系列函数,那铁定要跑 PHP 5.6;如果代码里已经换成mysqli或PDO,那 PHP 7.x 也能跑。我的建议是直接切到 PHP 5.6 兼容模式,理由就一条:这套代码的 sql 导入文件是 utf8_general_ci 排序规则,在老版本 MySQL 上兼容最稳。
# Windows 环境下的 PHPStudy 切换指令,假设安装在 D 盘 cd D:\phpstudy_pro\Extensions # 查看当前可用的 PHP 版本目录 ls | grep php逻辑说明:上面这两条命令用于确认本地是否具备多版本 PHP 切换能力。如果你用的是集成环境面板,通常在「软件管理 -> PHP」里直接切换,不一定要走命令行。参数说明:切换到 PHP 5.6 之后,记得在 php.ini 里打开php_mysqli.dll和php_gd2.dll两个扩展,GD2 缺失会导致验证码图片不显示。
2.2 数据库导入与站点根目录指向
源码包解压之后,你会看到一个sql文件夹,里面是完整的数据库备份。不要直接双击导入,先用记事本打开这个.sql文件,搜索一下有没有CREATE DATABASE语句。很多老源码里会写死数据库名,比如db_order,如果你本地的 MySQL 账号没有建库权限,导入就会失败。
-- 推荐做法:先手工建库,再导入 CREATE DATABASE IF NOT EXISTS `db_order` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; USE `db_order`; SOURCE D:/phpstudy_pro/WWW/order/sql/order.sql;逻辑说明:先建库再导表的好处是避免导入过程中因为字符集不匹配导致的中文乱码。SOURCE命令是 MySQL 命令行客户端的专用导入指令,它比在 phpMyAdmin 里粘贴 SQL 更不容易超时。参数说明:DEFAULT CHARACTER SET utf8指定了库的默认字符集,如果你的数据库连接串里没有设置charset=utf8,那页面查询出来的中文大概率是问号。
站点根目录指向是个容易被忽略的细节。这份源码的入口文件是index.php,如果直接放在WWW根目录下没有子文件夹,那需要把站点根目录指向WWW;如果放在WWW/order里,那站点根目录要指向order文件夹。常见做法是在站点配置里加一条伪静态规则,把非真实文件的请求转发到index.php:
location / { if (!-e $request_filename) { rewrite ^/(.*)$ /index.php?$1 last; } }逻辑说明:这段 Nginx 伪静态配置的作用是把美观的 URL(如/order/detail/5.html)重写成带参数的入口请求,同时保证静态文件(图片、CSS、JS)不被误改写。参数说明:$1在重写规则里代表捕获的路径部分,如果你的项目部署在子目录,需要把/index.php改成/order/index.php这种带前缀的写法。
3. 核心业务拆解:用户下单、商户接单、配送流转的完整链路
3.1 用户端点餐流程:购物车到订单提交的代码落点
用户端主流程是「选餐厅 -> 加购物车 -> 确认订单 -> 提交支付 -> 等待配送」。源码里对应的是index.php?m=shop&a=cart这一段。开发者在这个工程里把购物车实现做到了数据库层,也就是cart表,而不是用 Session 存临时数据,好处是换设备不丢购物车,坏处是数据库压力大。
// 加入购物车的核心逻辑,文件位置:controller/shop.php public function addCart() { $goods_id = intval($_POST['goods_id']); $num = intval($_POST['num']); if ($goods_id <= 0 || $num <= 0) { $this->json(0, '参数错误'); } $goods = $this->db->getRow("SELECT * FROM goods WHERE id = {$goods_id}"); if (!$goods) { $this->json(0, '商品不存在'); } $cart_data = [ 'user_id' => $_SESSION['uid'], 'shop_id' => $goods['shop_id'], 'goods_id' => $goods_id, 'num' => $num, 'price' => $goods['price'], 'add_time' => time(), ]; $this->db->insert('cart', $cart_data); $this->json(1, '已加入购物车'); }逻辑说明:这段代码的核心是先做参数合法性校验,再验证商品是否存在,最后组装数据入库。开发者用intval强制转换来防注入,这是一个老 PHP 项目里很典型的防护手法。参数说明:$_SESSION['uid']是用户登录后写入 Session 的用户标识,如果这个值是空的,说明用户没有登录,但代码里没有强制跳转,而是直接插入一条user_id=0的脏数据——这就是一个典型的业务漏洞,后面避坑部分会再提。
订单提交的代码在controller/order.php里,逻辑是:读取购物车数据 -> 计算总价 -> 生成订单号 -> 清空购物车。订单号的生成规则很有意思:
// 订单号生成规则 $order_sn = date('YmdHis') . rand(1000, 9999);逻辑说明:这个规则是「年月日时分秒 + 4 位随机数」,在并发量不高的场景下基本不会重复。参数说明:如果你的业务量一天超过一万单,这个规则就可能撞号,建议改成date('YmdHis') . mt_rand(10000, 99999)增加随机位数,或者直接用uniqid()再拼接一个随机串。
3.2 商户端接单与订单状态机
商户端的核心是对订单状态的流转控制。这份源码里的订单状态定义很直白:1 表示待接单,2 表示已接单,3 表示配送中,4 表示已完成,5 表示用户取消。整个状态机用order_status一个字段控制,虽然简单但够用。
// 商户端接单动作 public function accept() { $order_id = intval($_POST['order_id']); $shop_id = $_SESSION['shop_id']; $result = $this->db->query( "UPDATE orders SET order_status = 2 WHERE id = {$order_id} AND shop_id = {$shop_id}" ); if ($result) { $this->json(1, '接单成功'); } else { $this->json(0, '操作失败'); } }逻辑说明:这条 UPDATE 语句里带上了shop_id作为条件,作用是防止商户 A 操作商户 B 的订单。这是多商户系统里最基本的越权防护,代码水平在这个细节上体现得很清楚。参数说明:如果你的系统里同一个管理员可以管理多家店,那$_SESSION['shop_id']需要改成从门店管理员关联表里去查询,否则单店模式改多店时会直接踩坑。
订单状态机的流转约束在源码里是靠判断条件做的,没有用数据库触发器。比如用户取消订单,代码里会检查order_status是否小于 2,也就是只能取消待接单的订单:
// 用户取消订单 if ($order['order_status'] >= 2) { $this->json(0, '商家已接单,无法取消'); }逻辑说明:这个判断的实质是「状态小于 2 才允许取消」,也就是在待接单阶段用户有主动反悔的权利,一旦商户接单就只能联系商户手动处理。参数说明:很多现成的外卖平台把「商户接单后 15 分钟内可无理由取消」作为规则,如果你要复刻这种能力,需要加一个cancel_deadline时间字段,然后在取消判断里加上时间范围校验。
3.3 配送员端的定位与订单绑定
配送端的功能相对薄,主要是查看待配送订单、抢单、送达确认。源码里没有接入地图 API,用的是固定的配送范围字段delivery_range,单位是公里。这一点很适合新手学习,因为你不必处理高德或腾讯地图的鉴权配置。
// 计算配送距离是否在范围内 $distance = $this->getDistance($shop_lat, $shop_lng, $user_lat, $user_lng); if ($distance > $order['delivery_range']) { $this->json(0, '超出配送范围'); }逻辑说明:getDistance方法内部用的是球面距离公式,根据经纬度计算两点之间的距离。这个方案精度在城市级配送范围内够用,但不适合做骑行路线规划,因为它是直线距离,不包含实际道路约束。参数说明:经纬度字段在user_address表和shop表里都存在,如果导入数据后没有填充坐标,这段代码会拿到空值,floatval转换后变成 0,从而误判所有地址都在范围内。
4. 部署与二次开发避坑:五个把新手卡住三天的真实问题
4.1 数据库连接失败但 MySQL 明明正常运行
现象:安装完成之后,打开首页直接提示Database Connection Failed,后台管理面板也打不开。
原因:这份源码的数据库连接配置文件在config/config.php里,里面写死了localhost作为数据库主机地址。在 PHP 5.6 时代,localhost走的是 MySQL 的 socket 连接,而在新版 MySQL 8.0 默认是 TCP 连接,Socket 路径对不上,就报连接失败。
解决:把主机地址从localhost改成127.0.0.1,同时确认配置里的端口和你的 MySQL 实际端口一致(默认 3306)。改完之后重启一下 PHP 服务,再刷新页面。
注意:改
config.php后不要只刷新浏览器,必须重启 PHP 服务让配置重新加载,否则你改的东西可能没生效。
4.2 首页能开但验证码图片不显示
现象:登录页面加载出来了,输入框和按钮都在,但验证码图片的位置是一个裂开的图标。
原因:验证码生成依赖 GD 库,这个库不是 PHP 默认启用的。在 PHPStudy 里,PHP 5.6 版本的 GD 库默认可能是关闭状态,需要在扩展配置里手动勾选php_gd2.dll。还有一种可能是源码里调用的是imagecreate()函数,这函数在 GD 库低版本里存在,但在某些编译版本里被废弃,返回false。
解决:先在php.ini里确认extension=php_gd2.dll是否开启,然后写一个临时 PHP 文件执行<?php phpinfo(); ?>,搜索GD Support看是否为enabled。如果已经是enabled但图片还是不显示,检查验证码类文件(通常在include/validate_code.php)里输出的 header 头是不是在 HTML 内容输出之后才设置,如果是,那就把header('Content-Type:image/png')前面的所有空格和换行删掉。
4.3 购物车能加入但提交订单时报「非法请求」
现象:用户选择商品加入购物车没有问题,购物车列表也正常,但点击「去结算」按钮之后,跳转到一个错误页,提示「非法请求」。
原因:源码为了防跨站请求伪造,在表单里埋了一个token字段。这个 token 在生成订单时会被后端校验。如果你的站点配置了伪静态,而index.php在获取 POST 参数时用了$_REQUEST,那么 token 会在 URL 重写过程中被吞掉。
解决:找到订单提交的方法,把取参方式从$_REQUEST['token']改成$_POST['token']。同时检查模板里的表单是否真的把token字段渲染出来了,如果没有,在提交按钮前加一个<input type="hidden" name="token" value="<?php echo $_SESSION['token']; ?>">。
4.4 管理员后台登录后跳回登录页
现象:输入管理员账号密码,提示登录成功,页面跳转到后台地址,但马上又跳回登录页,Session 里存的值全部丢失。
原因:这是典型的 Session 路径配置问题。当站点根目录和 PHP 的 session 保存路径不一致时,PHP 创建的 Session 文件在另一个目录,而你的代码判断 Session 存在与否用的目录和它不是一个,所以每次刷新都像第一次访问。
解决:在config.php里增加一行session_save_path('/tmp');,并确认该目录有可写权限。如果是 Windows 环境,改成绝对路径,比如session_save_path('D:/phpstudy_pro/Extensions/tmp');,配置完重启 PHP。
4.5 上传的菜品图片在商家后台不显示
现象:商户后台上传菜品图片成功,预览时图片裂开,查看图片文件确实存在于upload/goods/目录下,但前端页面访问 404。
原因:这份源码的图片上传功能是普通的上传接口,没有做防盗链和路径鉴权。如果你把站点部署在二级目录(比如http://localhost/order/),而数据库里存的图片路径是/upload/goods/xxx.jpg,那页面访问时会从根目录去寻址,自然找不到。
解决:把所有图片字段的存储路径改成相对路径,去掉最前面的斜杠,也就是存upload/goods/xxx.jpg而不是/upload/goods/xxx.jpg。改完数据库之后,记得检查config.php里是否定义了UPLOAD_PATH常量,如果有,把它改成一个不带前导斜杠的相对路径。
提示:这个问题在源码自带的数据里尤其明显,因为作者开发时是在根目录跑的,一换环境就暴露。
5. 从「跑起来」到「能用起来」:三个必须做的二次开发动作
5.1 把订单号从「时间随机数」升级为「业务前缀 + 日期 + 自增 ID」
源码里的订单号规则date('YmdHis') . rand(1000, 9999)在小规模内测时问题不大,但如果你准备拿这套源码给真实商户做演示,建议改成带前缀的模式。常见做法是增加一个order_sn_prefix配置项,在生成订单号时拼接业务类型和来源渠道。
// 改造方案:订单号 = 前缀 + 日期 + 当天自增序号 $prefix = date('Ymd'); $today_count = $this->db->getOne("SELECT COUNT(*) FROM orders WHERE order_sn LIKE '{$prefix}%'"); $order_sn = $prefix . str_pad($today_count + 1, 6, '0', STR_PAD_LEFT);逻辑说明:这个方案的核心是用数据库查询当天已有订单数来生成序号,严格说查出来之后到插入之间有一个极小的时间窗口,理论上并发时可能撞号,但实际业务场景(单店日单量几百)基本不会触发。参数说明:如果你追求绝对唯一,可以改成uniqid($prefix, true),这样生成的是一个带前缀和时间戳的组合串,可读性差一点但绝对不重复。
5.2 给商家后台补一个「营业开关」
源码里商户端没有「开始营业 / 停止营业」的控制,商家只要登录后台,店铺在前台就一直展示。这在真实运营场景里很致命,尤其是商家休息、爆单或临时外出时。
-- 给 shop 表增加营业状态字段 ALTER TABLE `shop` ADD COLUMN `is_open` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '1营业 0休息';// 前端餐厅列表查询时过滤掉休息店铺 $where = " AND is_open = 1 "; $shop_list = $this->db->getAll("SELECT * FROM shop WHERE 1=1 {$where} ORDER BY id DESC");逻辑说明:这是一个典型的最小侵入改造,只需加一个字段,然后在查询列表处拼一个条件。商户后台的开关按钮可以复用现有的shop编辑功能,加一个复选框即可。参数说明:默认值设为 1 是为了兼容老数据,让已有的店铺不用手动开启也能正常展示;如果你希望新上线的店铺必须手动开店,那就把默认值改成 0。
5.3 配送费计算:从固定运费升级为距离动态计价
源码里的配送费是餐厅自己在后台填写的固定值,用户下单时不管距离远近都是同一个价格。真实场景里,超出基础距离应该加价。改造思路是在shop表增加两个字段:base_distance(基础公里数)和base_fee(基础配送费),外加一个per_km_fee(超出每公里加收费用),然后在订单金额计算时插入一段运费计算函数。
// 动态配送费计算示例 function calc_delivery_fee($distance, $shop) { if ($distance <= 0) return 0; $base_distance = floatval($shop['base_distance']); $base_fee = floatval($shop['base_fee']); $per_km_fee = floatval($shop['per_km_fee']); if ($distance <= $base_distance) { return $base_fee; } $extra_km = ceil($distance - $base_distance); return $base_fee + $extra_km * $per_km_fee; }逻辑说明:ceil向上取整的用法很关键,因为配送距离为 2.1 公里时,超出 1 公里的部分是 1.1 公里,按 2 公里算加价,这是外卖行业通用的计费方式,避免出现 0.1 公里的零头。参数说明:$shop数组里必须包含base_distance、base_fee、per_km_fee三个字段,如果数据库还没加这些字段就直接调用,PHP 会返回一个undefined index的警告,所以务必先执行上面的 ALTER 语句。
5.4 生产环境上线前,强制检查这三个配置
源码里的config.php在开发阶段为了方便,很多调试输出是开着的。上线前我会强制把DEBUG常量改成false,把数据库连接的错误提示关掉,不然 SQL 语句会明文渲染到页面上,等于把数据库结构直接暴露给用户。
另一个检查点是后台的默认管理员密码。源码安装包里一般会有初始账号,上线前必须第一步就把它改掉,不然网站等于裸奔。改的方式是直接编辑数据库表里的admin字段,用 MD5 加密后的新密码覆盖。
最后一个容易被忽略的是upload目录的写入权限。如果你把代码部署在 Linux 服务器上,upload/goods/这个目录需要给到 755 权限,而且所有者必须是 PHP 运行用户(通常是www或nobody)。如果权限低一档,商家上传图片时就会报「目录不可写」,排查下来最容易卡人的就是这个。
从那以后,我每接手一套 PHP 源码往生产环境放,都会强制走一遍「配置检查 -> 密码重置 -> 目录权限」三步,不是不信任源码,而是老项目里的默认值太容易被忽略了。希望这份外卖点餐源码能让你少走几段弯路,把时间花在真正有技术含量的事情上。
本文还有配套的精品资源,点击获取