1. 项目背景与需求分析
公交路线查询是城市居民日常出行的高频需求场景。传统公交查询方式存在几个痛点:官方APP体积臃肿、网页版查询需要反复输入站点、第三方应用广告干扰严重。基于PHP开发轻量级小程序恰好能解决这些问题——PHP作为服务端脚本语言处理能力强大,配合简洁的前端界面,可以实现以下核心功能:
- 实时公交到站预测(基于线路站点数据)
- 多站点换乘方案计算(A*算法优化路径)
- 用户常用路线收藏(Cookie/Session存储)
- 离线缓存基础线路(LocalStorage应用)
提示:选择PHP而非Node.js主要考虑中小城市公交系统的数据接口多为传统SOAP协议,PHP的SoapClient原生支持度更好
2. 技术架构设计
2.1 系统分层模型
采用经典三层架构:
表现层:微信小程序WXML+CSS 逻辑层:PHP7.4+Workerman长连接 数据层:MySQL8.0+Redis缓存2.2 关键数据表结构
CREATE TABLE `bus_lines` ( `line_id` int(11) PRIMARY KEY AUTO_INCREMENT, `line_name` varchar(20) NOT NULL COMMENT '线路名称如1路', `start_station` varchar(50) NOT NULL, `end_station` varchar(50) NOT NULL, `first_last_time` varchar(11) NOT NULL COMMENT '首末班时间如05:30-22:00' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `stations` ( `station_id` int(11) PRIMARY KEY, `station_name` varchar(50) NOT NULL, `geo_point` POINT NOT NULL SRID 4326, SPATIAL INDEX(`geo_point`) );注意:使用MySQL的空间索引(SPATIAL INDEX)能大幅提升附近站点查询效率
3. 核心功能实现
3.1 路线规划算法
采用改良A*算法,启发函数考虑三个因素:
- 站点间直线距离(Haversine公式计算)
- 历史平均候车时间(从Redis获取)
- 换乘惩罚系数(经验值设为1.8)
function aStarSearch($start, $end) { $openSet = new SplPriorityQueue(); $openSet->insert($start, 0); while (!$openSet->isEmpty()) { $current = $openSet->extract(); if ($current == $end) { return reconstructPath($cameFrom, $current); } foreach ($this->getNeighbors($current) as $neighbor) { $tentativeGScore = $gScore[$current] + $this->getDistance($current, $neighbor); if ($tentativeGScore < $gScore[$neighbor]) { $cameFrom[$neighbor] = $current; $gScore[$neighbor] = $tentativeGScore; $fScore = $gScore[$neighbor] + $this->heuristic($neighbor, $end); $openSet->insert($neighbor, -$fScore); // 优先级队列默认降序 } } } return false; }3.2 实时到站预测
通过两个数据源交叉验证:
- 公交公司API提供的GPS定位数据
- 用户众包上报的到站时间(需过滤异常值)
function predictArrivalTime($lineId, $stationId) { // 官方API数据 $officialTime = $this->getOfficialETA($lineId, $stationId); // 用户上报数据(取中位数) $userReports = $this->redis->zRange("line:$lineId:station:$stationId:reports", -5, -1, true); $medianReport = $this->calculateMedian(array_keys($userReports)); // 加权计算(官方数据权重0.7) return $officialTime * 0.7 + $medianReport * 0.3; }4. 性能优化实践
4.1 缓存策略设计
采用三级缓存体系:
- 热点线路数据:APCu内存缓存(TTL 30秒)
- 常规查询结果:Redis集群(TTL 5分钟)
- 静态线路信息:CDN边缘缓存(TTL 1小时)
4.2 数据库查询优化
针对高频的"附近站点"查询:
// 使用MySQL空间函数 $query = "SELECT station_id, station_name, ST_Distance_Sphere(geo_point, ST_SRID(Point(?, ?), 4326)) AS distance FROM stations WHERE MBRContains(ST_Buffer(ST_SRID(Point(?, ?), 4326), 0.01), geo_point) ORDER BY distance LIMIT 10"; // 使用预处理语句防止SQL注入 $stmt = $pdo->prepare($query); $stmt->execute([$lng, $lat, $lng, $lat]);5. 异常处理机制
5.1 数据源容错
当公交公司接口不可用时,自动降级方案:
- 使用最后有效数据+固定发车间隔估算
- 显示数据更新时间戳
- 引导用户贡献实时信息
5.2 高并发处理
针对早高峰的突发流量:
// 漏桶算法限流 $rateLimiter = new LeakyBucket( capacity: 1000, // 桶容量 leakRate: 100 // 每秒处理数 ); if (!$rateLimiter->tryAcquire()) { header('HTTP/1.1 429 Too Many Requests'); echo json_encode(['code' => 429, 'msg' => '系统繁忙,请稍后重试']); exit; }6. 部署实践
6.1 容器化部署
使用Docker-compose编排:
version: '3' services: app: image: php:8.2-fpm volumes: - ./src:/var/www/html depends_on: - redis - mysql mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: ${DB_PASSWORD} volumes: - mysql_data:/var/lib/mysql redis: image: redis:6.2-alpine ports: - "6379:6379" volumes: mysql_data:6.2 压力测试指标
使用ab工具测试结果:
Concurrency Level: 100 Time taken for tests: 5.012 seconds Complete requests: 5000 Failed requests: 23 Requests per second: 997.61 [#/sec] 90% Response Time: 98ms7. 实际踩坑记录
坐标系转换问题:
- 公交公司提供GCJ-02坐标
- 微信小程序使用WGS-84坐标
- 解决方案:引入coordtransform库转换
PHP-FPM进程阻塞:
- 同步调用第三方HTTP接口导致
- 改用Workerman异步HTTP客户端
MySQL空间索引失效:
- 需确保查询使用SRID 4326
- 错误示例:
ST_Distance(geo_point, Point(116.4, 39.9))(缺少SRID)
微信小程序域名限制:
- 每个接口需备案域名
- 使用Nginx反向代理统一域名
这个项目让我深刻体会到,看似简单的公交查询背后需要处理:坐标转换、实时数据融合、路径算法优化、高并发应对等一系列技术挑战。后续计划加入到站提醒推送功能,需要考虑iOS/Android不同的后台保活机制,这又会是新的技术攻关点。