拓冰建站拓冰建站
首页 / 资讯中心 / 正文

美业上门服务调度系统:PHP+Redis轻量级实现

简介这是一套基于PHP开发的微信公众号上门美容服务系统面向中小型美业机构及PHP初学者解决智慧美业O2O场景下的预约管理、服务派单、用户互动与后台运营等核心需求。资源包共2000个文件涵盖596个htm/379个html页面模板、546个js交互脚本、327个css样式文件支撑完整前端渲染与微信H5交互另有129个txt配置说明、14个md文档、1个sql数据库脚本便于快速部署与二次开发整体压缩包达114.97MB。已有96人学习下载适合希望掌握微信生态下PHP全栈开发实践的开发者。资源结构清晰含operamasks-ui等成熟UI组件集成预览可见多版本编辑器CSS适配如editor_ie8.css、editor_gecko.css体现对多浏览器及微信内置WebView的兼容性处理可直接用于本地调试、功能模块拆解或美业SaaS原型参考。1. 这不是又一个微信模板商城而是一套跑在真实美业门店手里的上门服务调度系统你见过凌晨两点还在接单的美容师吗她们手机里弹出的不是美团通知而是微信公众号里一条带客户地址、预约时间、皮肤类型标签和历史服务记录的结构化消息。这套系统不卖虚拟商品不搞裂变海报它把「预约→派单→定位签到→服务确认→评价回传→分账结算」全链路压进 PHP MySQL 微信 JS-SDK 的轻量栈里运行在普通阿里云 ECSCentOS 7 PHP 7.4 Nginx上。它没用 Laravel 框架核心调度逻辑写在app/controller/OrderDispatch.class.php里用 Redis 做实时抢单队列用curl_init()封装微信模板消息推送连美容师端的定位签到都靠wx.getLocation() 百度地图逆地理编码落地。适合年营收 50–300 万、3–15 名技师、日均订单 20–80 单的社区型美业机构——不是给程序员练手的 Demo是老板每天打开后台看技师履约率、客户复购率、时段空闲率的真实生产系统。2. 为什么选原生 PHP 而非 ThinkPHP 或 Laravel从美业业务约束反推技术选型2.1 美业场景对系统响应与部署的硬性约束上门美容服务的核心矛盾在于「时间颗粒度细、地理位置敏感、人员状态动态」。一个订单从客户提交到技师接单理想响应必须控制在 90 秒内技师在服务途中切换地址、临时加单、取消预约等操作需实时同步至调度台而门店老板往往只有一台 Windows 笔记本习惯用 Chrome 直接访问http://ip:8080/admin查数据。这些需求直接否定了重型框架的中间件链路、ORM 查询延迟和 Composer 自动加载开销。项目正文里反复出现的operamasks-ui.css和editor_ie*.css文件名恰恰印证了其兼容目标必须支持 IE8老店员用 Win7 自带 IE且前端组件要能直接嵌入微信内置浏览器WebView 内核版本碎片化严重。原生 PHP 的$_POST直取、file_get_contents()快速读取配置、json_encode()原生支持反而成了最稳的底座。提示不要被「PHP 过时」的舆论带偏。在 O2O 调度类系统中PHP 的过程式开发模式更贴近业务流——比如派单逻辑就是「查当前空闲技师 → 按距离排序 → 取前3人 → 推送模板消息 → 设置 60 秒超时锁」一行foreach加三行 SQL 就能闭环比写 Model 关系、定义 Event Listener 更快定位问题。2.2 核心模块的技术实现路径与关键文件映射系统采用典型的三层分离但未强制 MVC 目录结构所有业务逻辑集中在app/目录下模块关键文件技术要点验证方式微信接入core/wechat.class.php封装getAccessToken()、sendTemplateMsg()、checkSignature()手动处理 AES 解密消息体非 SDK用curl -X POST http://yourdomain.com/core/wechat.php -d xml...模拟微信服务器推送订单调度app/controller/OrderDispatch.class.php使用Redis::zAdd()构建「技师-距离」有序集合Redis::zRangeByScore()实时取最近3人避免 MySQLORDER BY distance LIMIT 3全表扫描在 Redis CLI 执行ZRANGEBYSCORE technician_distance 0 5000 WITHSCORES查结果服务签到app/api/signin.php调用wx.getLocation()获取经纬度 →file_get_contents(https://api.map.baidu.com/geocoder/v2/?akxxxlocationlat,lng)→ 解析 JSON 得到行政区划 →比对客户预约地址所属街道是否一致修改signin.php中file_get_contents()URL用 Postman 发送含lat39.915lng116.404的 POST 请求2.3 数据库设计如何支撑「上门」特性地理索引与状态机MySQL 表结构刻意规避复杂关联以提升高并发写入性能。关键表字段设计如下-- 订单主表精简版 CREATE TABLE orders ( id int(11) NOT NULL AUTO_INCREMENT, customer_id int(11) NOT NULL COMMENT 客户ID, address varchar(255) NOT NULL COMMENT 详细地址供人工核对, lng decimal(10,7) NOT NULL COMMENT 百度坐标经度, lat decimal(10,7) NOT NULL COMMENT 百度坐标纬度, status tinyint(1) NOT NULL DEFAULT 0 COMMENT 0待派单,1已派单,2服务中,3已完成,4已取消, dispatch_time int(11) DEFAULT NULL COMMENT 派单时间戳, PRIMARY KEY (id), KEY idx_lat_lng (lat,lng) -- 复合索引加速地理范围查询 ) ENGINEInnoDB; -- 技师表含实时位置 CREATE TABLE technicians ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(50) NOT NULL, current_lng decimal(10,7) DEFAULT NULL, current_lat decimal(10,7) DEFAULT NULL, last_update_time int(11) DEFAULT NULL COMMENT 最后定位上报时间戳, status tinyint(1) NOT NULL DEFAULT 1 COMMENT 1空闲,2服务中,3离线, PRIMARY KEY (id) ) ENGINEInnoDB;注意idx_lat_lng索引不能直接用于「查找 5 公里内技师」因为 MySQL 的 BTree 不支持球面距离计算。实际查询走的是WHERE lat BETWEEN ? AND ? AND lng BETWEEN ? AND ?先圈出矩形区域再用 PHP 计算sqrt(pow($lat1-$lat2,2)pow($lng1-$lng2,2))过滤——这是在无 GIS 插件前提下的务实妥协。3. 从零部署Nginx PHP 7.4 Redis 环境搭建与微信配置实操3.1 Linux 服务器基础环境安装CentOS 7必须关闭 SELinux 并开放必要端口否则微信 JS-SDK 的config接口会因跨域或权限拒绝失败# 关闭 SELinux永久 sudo sed -i s/SELINUXenforcing/SELINUXdisabled/g /etc/selinux/config sudo setenforce 0 # 开放端口微信回调需 80/443后台管理可走 8080 sudo firewall-cmd --permanent --add-port80/tcp sudo firewall-cmd --permanent --add-port443/tcp sudo firewall-cmd --permanent --add-port8080/tcp sudo firewall-cmd --reload # 安装 EPEL 源及基础包 sudo yum install epel-release -y sudo yum update -y # 安装 PHP 7.4使用 Remi 源 sudo yum install http://rpms.remirepo.net/enterprise/remi-release-7.rpm -y sudo yum-config-manager --enable remi-php74 sudo yum install php php-cli php-mysqlnd php-gd php-redis php-curl php-json php-mbstring -y # 安装 Redis sudo yum install redis -y sudo systemctl start redis sudo systemctl enable redis3.2 Nginx 配置要点解决微信 WebView 的特殊限制微信内置浏览器对Content-Security-Policy和X-Frame-Options极其敏感Nginx 配置必须显式放行server { listen 80; server_name yourdomain.com; root /var/www/html; # 必须允许微信域名嵌入 add_header X-Frame-Options ALLOW-FROM https://mp.weixin.qq.com; add_header Content-Security-Policy default-src self data: blob: https:; script-src self unsafe-inline unsafe-eval https:; style-src self unsafe-inline https:; img-src self data: https:;; # PHP 处理 location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } # 静态资源缓存 location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg)$ { expires 1y; add_header Cache-Control public, immutable; } }提示X-Frame-Options设为ALLOW-FROM https://mp.weixin.qq.com是微信 JS-SDK 正常调用wx.config()的前提否则页面会白屏且控制台报错「Refused to display ... in a frame because it set X-Frame-Options to deny」。3.3 微信公众号后台配置四步法服务器配置URLhttp://yourdomain.com/core/wechat.php需备案域名Token在core/wechat.class.php第 12 行$token your_custom_token;中设置必须与后台填写完全一致EncodingAESKey启用消息加密后生成填入文件中$encodingAesKey your_43char_key;JS 接口安全域名添加yourdomain.com注意不能带http://不能写 IP必须是已备案域名网页授权获取用户信息域名yourdomain.com回调页面/user/auth.php项目中已预置自动写入session[openid]模板消息在「功能 → 模板消息」中申请「预约成功通知」「服务开始提醒」「评价邀请」三个模板将模板 ID 填入core/wechat.class.php的templateIds数组public $templateIds [ order_success TM00001, // 预约成功 service_start OPENTM20001, // 服务开始 review_invite OPENTM40001, // 评价邀请 ];4. 订单调度逻辑深度解析Redis 有序集合如何实现毫秒级就近派单4.1 派单触发时机与数据准备派单并非在客户下单后立即执行而是进入「待调度队列」等待人工审核防刷单或自动触发配置开关。关键入口在app/controller/OrderDispatch.class.php的autoDispatch()方法public function autoDispatch($orderId) { // 1. 锁定订单防止重复派单 $lockKey order_lock:{$orderId}; if (!Redis::set($lockKey, 1, [NX, EX 30])) { return [code -1, msg 订单已被处理]; } // 2. 查询客户地址经纬度 $order Db::table(orders)-where(id, $orderId)-find(); $customerLng $order[lng]; $customerLat $order[lat]; // 3. 构建「技师-距离」有序集合单位米 $this-buildTechnicianDistanceZset($customerLng, $customerLat); // 4. 取距离最近的3名空闲技师 $candidates Redis::zRangeByScore(technician_distance, 0, 5000, [LIMIT [0, 3]]); // 5. 向候选人推送模板消息含一键抢单链接 foreach ($candidates as $tid) { $this-sendDispatchMessage($tid, $orderId); } }4.2buildTechnicianDistanceZset()的地理计算实现该方法是性能关键点它不依赖 MySQL 计算而是用 PHP 预计算并写入 Redisprivate function buildTechnicianDistanceZset($customerLng, $customerLat) { // 从数据库查出所有 status1空闲的技师 $technicians Db::table(technicians) -where(status, 1) -field(id, current_lng, current_lat) -select(); foreach ($technicians as $t) { if (empty($t[current_lng]) || empty($t[current_lat])) continue; // 使用 Haversine 公式计算球面距离单位米 $distance $this-haversineDistance( $customerLat, $customerLng, $t[current_lat], $t[current_lng] ); // 写入 Redis 有序集合score 为距离越小越近 Redis::zAdd(technician_distance, $distance, $t[id]); } } private function haversineDistance($lat1, $lng1, $lat2, $lng2) { $earthRadius 6371000; // 地球平均半径单位米 $lat1 deg2rad($lat1); $lng1 deg2rad($lng1); $lat2 deg2rad($lat2); $lng2 deg2rad($lng2); $dLat $lat2 - $lat1; $dLng $lng2 - $lng1; $a sin($dLat/2) * sin($dLat/2) cos($lat1) * cos($lat2) * sin($dLng/2) * sin($dLng/2); $c 2 * atan2(sqrt($a), sqrt(1-$a)); return $earthRadius * $c; }逻辑说明haversineDistance()返回的是两点间大圆距离米作为 Redis ZSET 的 score。zRangeByScore查询时0 5000表示「距离客户 5 公里以内」LIMIT 0 3取前 3 名。相比 MySQLORDER BYRedis ZSET 的时间复杂度是 O(log(N))1000 名技师查询耗时稳定在 0.8ms 内。4.3 抢单链路与状态同步从微信消息到数据库更新技师点击模板消息中的「立即抢单」链接跳转至http://yourdomain.com/app/order/take.php?oid123tid456该页面执行// app/order/take.php $orderId $_GET[oid]; $techId $_GET[tid]; // 1. 检查订单是否仍为「待派单」状态 $order Db::table(orders)-where(id, $orderId)-find(); if ($order[status] ! 0) { die(订单已被他人抢走); } // 2. 使用 Redis 事务保证原子性 $redis new Redis(); $redis-multi(); $redis-watch(order_status:{$orderId}); $redis-zRem(technician_distance, $techId); // 从候选池移除 $redis-exec(); // 3. 更新订单状态与技师绑定 Db::table(orders)-where(id, $orderId)-update([ status 1, technician_id $techId, dispatch_time time() ]); // 4. 向客户推送「已为您安排技师」模板消息 Wechat::sendTemplateMsg(order_assigned, $order[customer_id], [ first 您的美容服务已安排成功, keyword1 $order[address], keyword2 date(m-d H:i, $order[dispatch_time]), keyword3 张技师, remark 技师将在30分钟内到达请保持电话畅通 ]);参数说明zRem()移除技师 ID 是为了防止同一技师重复抢单watch()监控订单状态若期间被其他请求修改则exec()返回 false需重试。这种设计比数据库乐观锁更轻量且天然适配微信高并发点击场景。5. 排查高频故障从微信签名失败到 Redis 连接超时的实战诊断清单5.1 微信 JS-SDKconfig:invalid signature的五层排查法该错误占微信类系统故障的 70% 以上必须按顺序逐层验证层级检查项命令/操作预期结果L1URL 一致性当前页面 URL 是否与调用wx.config()的 URL 完全一致含http://、www.、/结尾在 Chrome 控制台执行location.href必须与公众号后台「JS 接口安全域名」下填写的域名匹配L2nonceStr 与 timestampnonceStr是否每次请求唯一timestamp是否为当前秒级时间戳查app/js/wx_config.php中time()和uniqid()调用timestamp与服务器时间误差需 7200 秒2 小时L3jsapi_ticket 获取core/wechat.class.php中getJsApiTicket()是否成功在服务器执行php -r require core/wechat.class.php; \$wnew Wechat(); var_dump(\$w-getJsApiTicket());返回非空字符串且expires_in 0L4签名原文拼接jsapi_ticket、noncestr、timestamp、url四者是否按字典序升序拼接手动拼接jsapi_ticketxxxnoncestryyytimestampzzzurlhttp://a.b.c/d与代码中sha1()输入字符串完全一致L5签名缓存jsapi_ticket是否过期后未刷新查 Redis 中wechat:jsapi_ticket的 TTLredis-cli TTL wechat:jsapi_ticketTTL 应 0若为 -2 则表示 key 不存在需检查getJsApiTicket()内部逻辑5.2 Redis 连接超时导致派单失败的应急处理当OrderDispatch.class.php中Redis::zRangeByScore()返回空数组常见原因及修复# 1. 检查 Redis 进程状态 sudo systemctl status redis # 2. 检查连接数是否打满默认 10000 redis-cli config get maxclients redis-cli info clients | grep connected_clients # 3. 查看慢查询10ms 视为慢 redis-cli slowlog get 10 # 4. 清理过期的技师位置数据避免 zset 膨胀 # 在 Redis CLI 中执行 ZREMRANGEBYSCORE technician_distance 1000000 9999999999 # 删除距离 1000km 的脏数据正常业务中不可能存在提示项目中technician_distanceZSET 未设置过期时间长期运行后可能积累无效数据。建议在buildTechnicianDistanceZset()开头添加清理逻辑Redis::zRemRangeByScore(technician_distance, 1000000, inf);5.3 客户端定位失败的兼容性方案微信 iOS 端wx.getLocation()常因系统权限或 WebView 版本返回fail system permission denied此时需降级// app/js/location.js function getLocationFallback() { // 方案1尝试 HTML5 Geolocation if (navigator.geolocation) { navigator.geolocation.getCurrentPosition( (pos) { sendToServer(pos.coords.latitude, pos.coords.longitude); }, (err) { // 方案2用腾讯地图 IP 定位精度 1km $.get(https://apis.map.qq.com/ws/location/v1/ip?keyYOUR_KEY, (res) { if (res.status 0) { sendToServer(res.result.location.lat, res.result.location.lng); } } ); } ); } }注意腾讯地图 IP 定位需在「腾讯位置服务」控制台开通并将apis.map.qq.com加入 Nginx 的Content-Security-Policy的connect-src白名单。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门