news 2026/9/29 18:20:18

PHP网约车H5打车系统:双端源码部署与订单流转实战解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PHP网约车H5打车系统:双端源码部署与订单流转实战解析

简介:这是一套基于PHP开发的网约车H5打车系统源码,完整涵盖乘客端与司机端,面向PHP开发者、移动端H5学习者及需要搭建网约车平台原型的个人或团队,适合作为毕设项目、课程实践或商业项目起步代码。压缩包共2000个文件,大小约147MB,其中包含591个md说明文档、442个js脚本、207个html页面、36个sql数据库脚本,以及css样式、json数据、sh部署脚本等,覆盖技术文档、前端交互、页面结构与数据存储等多个开发环节。已有292人浏览/学习,可作为网约车业务与技术实现的参考案例。源码实现了定位、费用计算、司机接单、订单推送、在线支付、评价体系等核心流程,并配有清晰的目录结构、数据库建表脚本与部署脚本,便于从零搭建运行环境,深入理解PHP后端接口与H5前端页面之间的协作方式,可用于二次开发或学习完整的网约车业务闭环。

1. PHP网约车H5打车系统:一套源码同时跑通乘客端和司机端

一个PHP技术栈的团队想验证网约车业务模型,最省力的路径不是去研究原生App,而是先找一套能同时覆盖乘客端和司机端的H5打车系统源码。这套系统用PHP做接口层,H5页面承担两个角色的交互,订单从发起到完成全链路都能在浏览器里跑通。对于需要快速出Demo、做业务验证的中小团队来说,这不只是省掉App审核周期的问题,更是把后端逻辑和前端页面一次拆清楚的捷径。文章会从源码结构、本地部署、订单流转和常见翻车点几个维度展开,目标是让你拿到这套源码后,一个晚上就能把乘客叫车、司机接单的闭环转起来。

2. 拆解乘客端与司机端:H5双端的职责划分和通信链路

2.1 乘客端H5的页面流与接口依赖

乘客端的核心诉求是「发单快、看得见进度」。一个标准H5打车页面的流程大致是:手机号验证码登录 → 填写起终点 → 创建订单 → 等待司机接单 → 查看司机到达位置 → 行程中查看轨迹 → 支付。

这套流程在H5里对应着清晰的页面栈和接口依赖。登录页对应passenger/login,发单页对应passenger/create_order,等待接单页轮询passenger/order_status,支付页调用passenger/pay_order。

页面之间的跳转用location.href就能完成,不需要引入重型路由框架。但要注意,乘客端页面结构如果按功能模块拆目录,会让后续维护轻松很多。比如views/passenger/下再分login.html、create_order.html、order_detail.html、pay.html,静态资源和接口封装放assets/和api/目录。

乘客端H5对接口的依赖有一个关键特点:大部分操作轮询可以解决。比如等待司机接单这个环节,前端用setInterval每2秒请求一次order_status接口,根据返回的status字段跳转到对应页面或刷新UI状态。这种轮询模式虽然不如WebSocket实时,但胜在实现简单,也是这套PHP源码里最常见的通信方式。

2.2 司机端H5的订单流与状态机

司机端和乘客端的最大区别在于:司机端是「被订单推着走」的,页面状态必须跟随订单状态同步更新。司机端的主要页面有:登录认证页、听单页(可切换在线/离线)、新订单提醒页、订单详情页(去接乘客、开始行程、完成订单)、收入统计页。

司机端最常见的问题是「司机在接单后去乘客端页面刷新,订单状态丢失」。这通常是因为前端只做了局部状态管理,没有统一从接口同步数据。我的经验是司机端每个页面进入时都调用一次driver/current_order接口,用接口返回的订单状态来渲染页面,而不是依赖上个页面传递的参数。

订单状态机是整个系统的核心。常见的状态定义如下:

// 订单状态常量定义 define('ORDER_STATUS_PENDING', 0); // 待接单 define('ORDER_STATUS_ACCEPTED', 1); // 司机已接单 define('ORDER_STATUS_ARRIVED', 2); // 司机已到达 define('ORDER_STATUS_DRIVING', 3); // 行程进行中 define('ORDER_STATUS_COMPLETED',4); // 已完成 define('ORDER_STATUS_CANCELED', 5); // 已取消

这个状态机的推进方向必须是单向的。pending可以走向accepted或canceled,accepted可以走向arrived或canceled,但completed不能回退到driving。状态流转的后端校验是这套系统最重要的安全边界,后面第4章会专门讲并发更新的写法。

2.3 双端共用的PHP接口层设计

乘客端和司机端虽然是两套页面,但后端接口可以共用一套。常见的做法是用URL前缀区分角色,比如/api/passenger/*和/api/driver/*,然后通过中间件做权限校验。

接口层设计上,最关键的是统一返回格式。PHP里最常见的写法是这样:

<?php // 统一响应封装 function json_response($code, $msg, $data = []) { header('Content-Type: application/json; charset=utf-8'); echo json_encode([ 'code' => $code, // 0表示成功,非0表示业务错误 'msg' => $msg, // 给前端展示的提示信息 'data' => $data // 业务数据,可以是数组、对象或嵌套结构 ]); exit; } // 使用示例:返回乘客信息 json_response(0, 'success', [ 'user_id' => 1001, 'mobile' => '138****1234', 'role' => 'passenger' ]);

这里的data字段就是热词里说的「php接口数组对象」——它既可能是索引数组(比如司机列表),也可能是关联数组(对应前端的JSON对象),PHP用json_encode统一处理,前端拿到后直接当作对象用。要注意把数值字段转成int或float,避免前端拿到的distance是字符串,导致计算出错。

接口层还需要做角色权限校验。常见做法是登录后返回一个token,前端存到localStorage,每次请求通过Authorization头携带,后端用$_SERVER['HTTP_AUTHORIZATION']读取并校验。写PHP源码的时候,建议把校验逻辑做成一个require_auth()函数,放在每个业务接口的入口处。

3. 本地跑通最小系统:部署环境、数据库配置和首次叫单验证

3.1 环境准备:PHP版本、扩展和伪静态规则

这套源码属于典型的PHP MVC结构,对运行环境的要求并不苛刻。常见做法是用PHP 7.2以上版本配合Nginx运行,需要确保以下扩展已启用:pdo_mysql(数据库连接)、mbstring(中文处理)、curl(调用第三方地图和支付接口)、openssl(支付回调验签)、redis(可选,用于缓存和队列)。

这些扩展里最容易漏掉的是mbstring。H5页面里大量中文参数要urlencode传输,没有mbstring会导致json_encode出错,返回的中文变成乱码或者直接报错。PHP 7.2的环境检查可以用一行命令完成:

php -m | grep -E 'pdo_mysql|mbstring|curl|openssl|redis'

如果redis没装,也不要慌,源码一般会提供file驱动作为缓存降级方案。在config.php里把cache_driver改为file,并保证runtime/cache目录可写即可。这一步是很多人部署时卡住的地方——mysql驱动没问题,但redis连接失败导致所有接口白屏。

3.2 数据库初始化与配置文件的三处必改

拿到源码后,第一步不是改代码,而是建库导入。

源码目录下一般会提供database.sql或install.sql文件,使用mysql命令行导入即可:

mysql -uroot -p -e "CREATE DATABASE IF NOT EXISTS taxi DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" mysql -uroot -p taxi < database.sql

数据库导入完成后,一共会看到约10张左右的业务表。除了用户表、订单表、司机信息表之外,还有一张config表专门存系统配置。导入后要立刻检查orders表的索引情况,重点看status字段和create_time字段是否有索引,这直接决定后面轮询接口的性能。

接下来是配置文件的三处必改项。PHP项目最常见的配置入口是根目录下的config.php:

<?php // config.php 核心配置 return [ // 第一处:数据库配置 'db' => [ 'host' => '127.0.0.1', 'port' => 3306, 'name' => 'taxi', 'user' => 'root', 'pass' => '你的数据库密码', 'charset' => 'utf8mb4' ], // 第二处:H5访问域名(乘客端和司机端用不同前缀访问) 'app_url' => [ 'passenger' => 'http://passenger.yourdomain.com', 'driver' => 'http://driver.yourdomain.com' ], 'img_url' => 'http://img.yourdomain.com', // 图片上传后的访问域名 // 第三处:地图服务的Key与Secret 'map' => [ 'key' => '你的地图应用Key', 'secret' => '你的地图应用Secret' ], ];

这里有个很常见的坑:app_url配置的域名和实际访问域名不一致。前端H5请求接口用的是相对路径/api/...,后端生成的分享链接或支付回调需要绝对域名,如果配置里的域名写的是localhost而实际用IP访问,微信支付回调就会拿到错误的主机名。

配置好之后,建议用一个PHP命令行脚本验证数据库连通性:

php -r "require 'config.php'; \$pdo = new PDO('mysql:host=' . \$config['db']['host'] . ';dbname=' . \$config['db']['name'], \$config['db']['user'], \$config['db']['pass']); echo 'DB OK';"

能输出DB OK说明配置没有问题。这里用PHP命令行的好处是绕开Nginx,可以直接定位是PHP问题还是Web服务器问题。

3.3 启动双端H5:Nginx站点配置与URL改写

H5项目部署到Nginx,最核心的是伪静态配置。这套源码如果是标准的MVC路由,通常需要把所有请求转发到index.php入口文件。

乘客端和司机端虽然是两套H5页面,但可以放在同一个站点下,通过目录区分。Nginx站点配置里,我一般会把/passenger/前缀的请求映射到乘客端目录,/driver/前缀映射到司机端目录,/api/前缀的请求全部走PHP统一入口:

server { listen 80; server_name yourdomain.com; root /var/www/taxi/public; index index.php index.html; # H5页面:乘客端 location /passenger/ { alias /var/www/taxi/passenger/; try_files $uri $uri/ /passenger/index.html; } # H5页面:司机端 location /driver/ { alias /var/www/taxi/driver/; try_files $uri $uri/ /driver/index.html; } # PHP接口:统一入口 location /api/ { try_files $uri /index.php$is_args$args; } location ~ \.php$ { include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_pass 127.0.0.1:9000; } }

这里的try_files规则是为了解决H5路由刷新404的问题。乘客端页面在浏览器里滚动到/passenger/order_detail.html时,如果写成/passenger/order_detail这样不带.html的路径,try_files会先找文件,找不到就回退到index.html,由前端JS接管路由。

3.4 首次跑通叫单流程:一条curl命令验证接口

双端部署完成后,先别急着打开浏览器,用curl直接验证接口能更快定位问题。以乘客登录接口为例:

curl -X POST http://yourdomain.com/api/passenger/login \ -H "Content-Type: application/json" \ -d '{"mobile":"13800138000","code":"123456"}'

正常返回格式应该是:

{"code":0,"msg":"success","data":{"user_id":1001,"token":"abc123"}}

如果返回是{"code":4001,"msg":"验证码错误"},说明验证码校验逻辑通走了,是预期行为。如果返回是HTML错误页或空响应,则需要检查Nginx错误日志和PHP错误日志:

tail -f /var/log/nginx/error.log tail -f /var/log/php7.4-fpm.log

日志里最常见的两类错误:一是File not found,说明try_files规则没写对;二是PHP Fatal error: Uncaught PDOException,说明数据库配置有问题。把这两个日志看住,部署阶段90%的问题都能当场定位。

4. 订单流转的数据库设计与关键SQL:从建表到就近派单

4.1 订单表、司机表、乘客表的核心字段设计

打车系统的数据模型围绕订单展开,核心表是orders,它关联乘客表和司机表。这三张表的字段设计决定了整个系统的业务边界。

先看用户表和司机信息表。用户表存通用账号信息,司机信息表用user_id关联用户表,存司机的接单资质信息:

-- 用户表:乘客和司机共用 CREATE TABLE `member` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `mobile` VARCHAR(20) NOT NULL COMMENT '手机号', `role` TINYINT NOT NULL DEFAULT 1 COMMENT '1乘客 2司机', `status` TINYINT NOT NULL DEFAULT 1 COMMENT '1正常 0禁用', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_mobile` (`mobile`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; -- 司机信息表 CREATE TABLE `driver_info` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `user_id` INT UNSIGNED NOT NULL COMMENT '关联member.id', `real_name` VARCHAR(50) NOT NULL COMMENT '司机姓名', `car_number` VARCHAR(20) NOT NULL COMMENT '车牌号', `car_model` VARCHAR(50) NOT NULL COMMENT '车型', `current_lng` DECIMAL(10,6) DEFAULT NULL COMMENT '当前经度', `current_lat` DECIMAL(10,6) DEFAULT NULL COMMENT '当前纬度', `online_status` TINYINT NOT NULL DEFAULT 0 COMMENT '0离线 1在线', PRIMARY KEY (`id`), KEY `idx_user_id` (`user_id`), KEY `idx_online_status` (`online_status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='司机信息表';

订单表则是整个业务的中枢,它需要记录从发单到支付的全过程字段:

CREATE TABLE `orders` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `order_no` VARCHAR(32) NOT NULL COMMENT '订单号', `passenger_id` INT UNSIGNED NOT NULL COMMENT '乘客user_id', `driver_id` INT UNSIGNED DEFAULT NULL COMMENT '司机user_id', `start_lng` DECIMAL(10,6) NOT NULL COMMENT '出发经度', `start_lat` DECIMAL(10,6) NOT NULL COMMENT '出发纬度', `end_lng` DECIMAL(10,6) NOT NULL COMMENT '终点经度', `end_lat` DECIMAL(10,6) NOT NULL COMMENT '终点纬度', `status` TINYINT NOT NULL DEFAULT 0 COMMENT '订单状态', `distance` INT UNSIGNED DEFAULT NULL COMMENT '预估里程(米)', `amount` DECIMAL(10,2) DEFAULT NULL COMMENT '预估金额', `actual_amount` DECIMAL(10,2) DEFAULT NULL COMMENT '实际金额', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `accept_time` DATETIME DEFAULT NULL COMMENT '接单时间', `finish_time` DATETIME DEFAULT NULL COMMENT '完成时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_order_no` (`order_no`), KEY `idx_status` (`status`), KEY `idx_passenger_id` (`passenger_id`), KEY `idx_driver_id` (`driver_id`), KEY `idx_create_time` (`create_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';

字段类型的选择上有几个细节值得注意。

  • 经纬度用DECIMAL(10,6),不用FLOAT或DOUBLE,因为FLOAT的精度误差会导致距离计算偏差几十米,这在打车场景里乘客能直接感知到。
  • 金额用DECIMAL(10,2),避免FLOAT的浮点误差导致对账不平。
  • 距离字段用整数米的单位,不用公里小数,因为公里小数在结算时容易产生舍入争议。
  • 每个订单号要唯一,用UNIQUE KEY约束,order_no通常由时间戳加随机数生成。

4.2 距离计算:Haversine公式在SQL里的写法

H5打车系统里一个基础能力是计算乘客起点和司机当前位置的距离。这个计算在派单和费用预估场景都会被调用。常用的算法是Haversine公式,它考虑地球曲率,在几公里的范围内精度足够。

SQL里直接写这个公式的复杂度不高,但要注意RADIANS()函数使用:

-- 计算乘客起点附近的在线司机,按距离排序 SELECT d.id, d.user_id, d.car_number, ROUND( 6371 * ACOS( COS(RADIANS(%s)) * COS(RADIANS(d.current_lat)) * COS(RADIANS(d.current_lng) - RADIANS(%s)) + SIN(RADIANS(%s)) * SIN(RADIANS(d.current_lat)) ) * 1000 ) AS distance_meter FROM driver_info d WHERE d.online_status = 1 AND d.current_lng BETWEEN %s - 0.05 AND %s + 0.05 AND d.current_lat BETWEEN %s - 0.05 AND %s + 0.05 HAVING distance_meter < %s ORDER BY distance_meter ASC LIMIT 10;

这里的%s参数依次是:乘客起点纬度、乘客起点经度、乘客起点纬度、经度范围下限、上限、纬度范围下限、上限、最大距离。

其中BETWEEN范围限定很关键,它相当于在查询前先圈定一个经纬度矩形,把全表扫描变成小范围过滤。5公里距离对应大约0.05度的经纬度范围,这是一个经验值,可以按城市纬度微调。如果没有这个范围限制,每次派单都要全表扫描所有在线司机,司机数量一多,SQL响应就会明显变慢。HAVING子句里的距离过滤是为了精确剔除矩形四个角上超出实际半径的司机。

有些场景会用空间索引SPATIAL INDEX配合ST_Distance()做更高效的距离查询,但PHP源码里用Haversine公式加矩形范围过滤已经够用,而且兼容老版本MySQL。

4.3 就近派单逻辑的PHP实现

派单是打车系统的核心决策:新订单进来后,要在在线司机里找最近的一位去接单。常见的策略是「就近派单」——把订单推给距离起点最近的在线司机。

PHP侧实现可以拆成两步:先查候选司机,再尝试更新司机接单状态:

<?php /** * 就近派单 * @param int $orderId 订单ID * @param float $lat 乘客起点纬度 * @param float $lng 乘客起点经度 * @return bool 是否派单成功 */ function dispatch_order($orderId, $lat, $lng) { $pdo = get_db_connection(); // 第1步:查出距离乘客起点最近的10个在线司机 $sql = "SELECT d.user_id, d.current_lat, d.current_lng, (6371 * ACOS(COS(RADIANS(:lat)) * COS(RADIANS(d.current_lat)) * COS(RADIANS(d.current_lng) - RADIANS(:lng)) + SIN(RADIANS(:lat)) * SIN(RADIANS(d.current_lat)))) AS distance FROM driver_info d WHERE d.online_status = 1 ORDER BY distance ASC LIMIT 10"; $stmt = $pdo->prepare($sql); $stmt->execute(['lat' => $lat, 'lng' => $lng]); $candidates = $stmt->fetchAll(PDO::FETCH_ASSOC); if (empty($candidates)) { return false; // 附近没有在线司机 } // 第2步:按优先级依次尝试派单,抢到就停 foreach ($candidates as $driver) { // 更新司机在线状态为“已派单”,条件是当前仍然在线 $updateSql = "UPDATE driver_info SET online_status = 2 WHERE user_id = :userId AND online_status = 1"; $updateStmt = $pdo->prepare($updateSql); $updateStmt->execute(['userId' => $driver['user_id']]); // rowCount为1表示抢占成功,这个司机被锁定 if ($updateStmt->rowCount() > 0) { // 把订单分配给这个司机 $assignSql = "UPDATE orders SET driver_id = :driverId, status = 1, accept_time = NOW() WHERE id = :orderId AND status = 0"; $assignStmt = $pdo->prepare($assignSql); $assignStmt->execute([ 'driverId' => $driver['user_id'], 'orderId' => $orderId ]); return $assignStmt->rowCount() > 0; } } return false; }

这段代码有两个隐藏的关键逻辑。

一是UPDATE driver_info SET online_status = 2 WHERE user_id = :userId AND online_status = 1的并发控制。online_status从1到2的更新条件带上了原状态,MySQL的rowCount()返回1说明只有一个请求能成功修改。否则10个订单同时派给同一个司机,司机端会同时弹出多个接单提醒。

二是订单更新的status = 0条件。它保证订单只从待接单状态变成已接单,如果乘客已经取消订单,这个UPDATE的影响行数为0,派单自然失败。

这两个条件就是最朴素的乐观锁写法,不需要引入Redis分布式锁,单机MySQL部署时也能正常工作。

4.4 支付回调和状态流转的SQL:影响行数比查询更可靠

支付是另一个容易踩坑的环节。乘客支付成功后,第三方支付平台会回调PHP接口,回调里更新订单状态。

常见的错误写法是:先查询订单状态,再决定是否更新。但回调可能存在并发——用户连续点击支付,同一个订单收到两次回调,两次查询都读到未支付,然后各自执行更新,导致金额重复入账。

正确的做法是把状态判断写进UPDATE语句里:

UPDATE orders SET status = 4, pay_time = NOW(), actual_amount = :amount, pay_channel = :channel WHERE order_no = :orderNo AND status = 3;

执行后检查rowCount(),为1说明本次更新生效,是第一次回调;为0说明之前已经更新过,直接返回成功给支付平台即可。这个写法的好处是不需要事务和锁,单条SQL的原子性就能保证幂等。

订单变更类操作全部采用这种「状态条件更新 + 影响行数判断」的模式后,整个系统的状态不一致问题会大幅减少。这一习惯值得在写PHP代码时从第一张订单表开始就坚持。

5. 避坑记录:双端H5打车项目的5个常见翻车点

5.1 H5在iOS下载文件变成了预览:文件头没设对

现象:司机在H5里点击下载行程单,安卓手机上正常弹出下载,iPhone上却直接打开预览。

原因:iOS的Safari对Content-Type为application/octet-stream的附件会优先尝试预览,而不是下载。PHP里输出文件时没有设置Content-Disposition: attachment响应头。

解决:PHP输出文件下载前必须明确指定下载头:

header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename="' . $fileName . '"'); header('Content-Length: ' . filesize($filePath));

同时要注意,Content-Disposition里的文件名如果有中文,需要先做urlencode,否则iOS上文件名会乱码。这个坑在H5打车场景里尤其常见,因为行程单、发票都是用户高频下载的文件。

5.2 跨域拦截:PHP接口没开CORS导致乘客端白屏

现象:乘客端H5部署在passenger.xxx.com,PHP接口部署在api.xxx.com,打开页面时所有请求都被浏览器拦截,控制台报No 'Access-Control-Allow-Origin' header is present。

原因:前后端分离部署后,H5页面和PHP接口属于不同域名,产生了跨域请求。浏览器默认不允许跨域读取响应。这是一个纯粹的前后端分离架构问题,和PHP本身无关。

解决:两种常用方案。第一种是PHP接口层统一添加CORS响应头:

header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Methods: GET, POST, OPTIONS'); header('Access-Control-Allow-Headers: Content-Type, Authorization');

第二种是走JSONP,但JSONP只支持GET请求,POST接口没法用。当前端需要携带Authorization头时,记得在Access-Control-Allow-Headers里显式加上,否则预检请求OPTIONS会失败。H5继续白屏,接口层日志里又看不到任何报错。

5.3 H5 blob文件能上传吗:图片走后台上传翻车

现象:司机端上传驾驶证照片,前端用canvas压缩后生成blob对象,直接放进FormData传给PHP接口,后端收到的$_FILES是空的。

原因:H5的blob文件需要指定文件名才能被PHP识别。FormData.append('file', blob)如果没给第三个参数,浏览器生成的临时文件名没有.jpg后缀,部分PHP版本会拒绝识别为上传文件。

解决:追加blob时显式指定文件名:

// H5前端:blob对象必须带文件名 const formData = new FormData(); formData.append('file', blob, 'driver_license_' + Date.now() + '.jpg'); formData.append('type', 'license');

后端PHP按普通$_FILES处理即可,同时校验文件类型白名单和后缀名。这个坑属于H5独有的,原生App上传文件时不会暴露,但H5的压缩裁剪功能绕不开blob。

5.4 WebSocket没部署,司机接单全靠接口轮询

现象:司机端听单页每5秒查询一次新订单,乘客端发单后,司机端的提醒最长延迟5秒。高峰期时,一个订单同时被多个司机看到,抢单按钮点下去频繁提示已被抢走。

原因:源码为了降低部署门槛,接单提醒用的是HTTP轮询而不是WebSocket。轮询间隔短会打爆PHP-FPM进程,间隔长则体验变差。

解决:如果短期不打算上WebSocket,可以用Redis的列表结构做一个轻量「推送队列」。PHP订单创建后把订单号LPUSH到order_queue,司机端轮询时改用BRPOP阻塞读取,响应延迟从5秒降到毫秒级,同时PHP侧压力大幅下降。

// PHP:创建订单后推入Redis队列 $redis->lpush('order_queue', json_encode([ 'order_id' => 123, 'lat' => $startLat, 'lng' => $startLng ]));

司机端的轮询改为常驻的BRPOP请求,PHP接口挂起等待队列数据返回。这样做不用部署额外的推送服务,就能把实时性提升一个量级。只有当订单量真的上来之后,再考虑替换成WebSocket或第三方推送服务。

5.5 并发状态更新:乘客取消和司机接单撞在一起

现象:乘客发起取消订单的同时,司机刚好点了接单。乘客端显示已取消,司机端显示已接单,两边状态不一致,且司机端已经开车前往起点。

原因:乘客取消操作和司机接单操作是两个独立的接口,它们都更新同一张订单表。PHP进程并发执行时,先到的更新了status,后到的因为读到了旧状态,也更新成功,最终后更新的把先更新的覆盖了。

解决:像第4.4节那样,把所有状态变更都写成条件UPDATE:

// 司机的接单操作,必须保证当前订单状态仍为“待接单” UPDATE orders SET status = 1, driver_id = :driverId WHERE id = :orderId AND status = 0; // 乘客的取消操作,必须保证当前订单没有进入已接单 UPDATE orders SET status = 5, cancel_reason = :reason WHERE id = :orderId AND status IN (0, 1);

按这个写法,司机接单时如果乘客已取消,前一条更新的影响行数为0,接口直接返回「订单已取消」,司机端就不会产生误接单。

6. 把Demo做成能落地的系统:地图、推送与安全三件事

6.1 地图:API Key别写在前端

H5页面的JavaScript代码对用户完全可见。地图服务的Key一旦写进前端代码里,任何人打开浏览器就能窃取,别人用你的Key调地图API产生的费用都会记在你的账上。正确的做法是把地图相关的请求统一收敛到PHP后端。前端只传起终点经纬度,PHP接口调用地图服务做路线规划、距离计算,返回结果给前端渲染。这样Key只保存在服务器端,前端拿到的只是计算结果。

6.2 支付与回调:用Redis队列做订单超时处理

订单创建后超过一段时间没人接,系统要自动取消。最简单的方式是每分钟扫描一次数据库,把超时的待接单订单一并取消。这个方案在订单量大时会拖慢数据库。更轻量的做法是创建订单时写一条Redis带过期时间的Key,用Key过期事件触发取消逻辑,或者用Redis的有序集合存储订单到期时间戳,一个定时任务只处理到期的订单。POC阶段每分钟一次数据库扫描其实也能撑住,什么时候换取决于订单规模。

6.3 安全:接口签名和参数校验

H5打车系统的接口直接暴露在浏览器里,前端传上来的任何参数都不能信任。乘客端传价格、传里程这类关键字段,后端必须重新计算,不能直接用前端值。所有接口至少要做一个简单的签名校验:前端用约定密钥对参数排序后拼接、计算MD5,PHP后端用同一密钥重算一遍。这个方案不复杂,但能挡住绝大多数的接口篡改请求。

什么时候把Demo做成生产系统,我自己的判断标准是三条:支付对账做了没有、司机刷单防了没有、订单超时机制写了没有。这套PHP源码最大的价值在于把双端业务流程完整串起来,业务逻辑能跑通一个完整闭环,但距离生产还有一段路。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 18:19:05

SRGAN超分辨率重建实战:对抗训练如何重构图像高频细节

简介&#xff1a;SRGAN超分辨率重建项目完整源码包&#xff0c;面向深度学习、图像处理方向的开发者与研究者&#xff0c;核心解决低分辨率图像到高分辨率图像的细节恢复与纹理增强问题。压缩包共29个文件&#xff0c;其中8个Python脚本覆盖数据预处理、网络结构、感知损失与对…

作者头像 李华
网站建设 2026/9/29 18:18:54

C++设计原则实战:八大原则如何指导现代C++代码重构

1. 为什么这八大原则不是“教条”&#xff0c;而是你写C时每行代码背后的呼吸节奏你有没有过这样的时刻&#xff1a;刚写完一个类&#xff0c;编译通过、功能跑通&#xff0c;心里还美滋滋地想着“这设计真优雅”&#xff1b;结果两周后加个新需求&#xff0c;改三行代码却要动…

作者头像 李华
网站建设 2026/9/29 18:17:59

Starnet实用指南:低照度图像增强网络与星型拓扑管理

1. 项目概述与背景解析 1.1 starnet是什么&#xff1a;从名字看本质 第一次听到“starnet”这个名字&#xff0c;很多人第一反应是“星际网络”“星星网络”之类的科幻联想。实际上&#xff0c;starnet并不是一个单一的开源项目或商业产品&#xff0c;而是一个在不同技术语境下…

作者头像 李华
网站建设 2026/9/29 18:17:51

JavaEE图书管理系统:环境适配、事务一致性与数据库可重演

简介&#xff1a;这是一套完整可用的JavaEE图书管理系统实战项目&#xff0c;面向计算机专业本科生、毕设学生及Java初学者&#xff0c;助力课程设计、期末大作业与项目能力提升。资源包含248个文件&#xff0c;涵盖35个核心Java源码、70个编译后Class文件、18个JSP页面、14个H…

作者头像 李华
网站建设 2026/9/29 18:15:47

STM32CubeIDE中printf浮点数输出异常的根源与5分钟修复方案

1. 项目概述&#xff1a;为什么STM32CubeIDE里printf一打浮点数就卡死、乱码或输出0.000000&#xff1f;刚在STM32CubeIDE里敲下printf("PI %.6f\r\n", 3.1415926f);&#xff0c;串口调试助手却只收到PI 0.000000&#xff0c;或者干脆没反应——你不是一个人。这问…

作者头像 李华
网站建设 2026/9/29 18:15:23

2027创新计算机选题:FitCore 智能运动健康计算平台

1. 项目定位 FitCore 是面向 2027 年的运动健康个人计算机新形态&#xff0c;以 动作捕捉 运动数字孪生 AI 教练 为核心&#xff0c;让计算机成为"随身私人教练与运动健康管家"。维度说明核心能力动作捕捉、运动孪生、AI 教练目标用户大众健身、竞技体育、康复医疗…

作者头像 李华