简介:这是一套同城上门家政/按摩预约系统的H5小程序源码,面向家政公司、本地生活服务创业者或具备基础前端能力的开发者。整套资源包含完整的前端工程与前后端交互代码,涵盖服务项目展示、上门预约、订单管理、会员与支付配置等常见业务模块,可直接作为二次开发或学习参考的基础脚手架。压缩包共2000个文件,约66.31MB,以JavaScript、Vue页面、Markdown说明文档为主,辅以JSON配置文件、HTML静态页、CSS样式以及SQL数据库脚本,目录结构较清晰,便于按模块定位所需源码。目前已有238人学习/下载。通过梳理源码可掌握同城预约类小程序的页面组织、接口对接与业务逻辑实现思路,尤其适合希望快速搭建家政按摩上门服务场景的初学者或产品原型验证者。
1. 同城上门家政按摩H5小程序源码:先看清这个zip里装的是什么
拿到「同城上门家政按摩H5小程序源码 上门预约系统.zip」,先别急着解压。这个包里装的不是模板,而是一整套上门预约生意的闭环:用户端H5、小程序端、技师端、管理后台、后端接口与初始化数据库都在里面。目标就一条:让你尽快跑起来、接得住真实订单。
做这行的人最清楚,上门预约看着只是「用户约技师、技师上门」两句话,落地却是一堆麻烦:服务项目怎么挂、技师档期怎么排、距离怎么算、谁派单、支付完怎么通知、取消退款怎么办。这套系统的价值,就是把这些场景的默认方案摆在你面前,给你一个能对照、能改造的起点。
下面按「业务设计→本地跑通→链路联调→踩坑排查→上线前准备」推进,照着做,就能把这个包变成你自己的预约系统。
2. 上门预约系统骨架:状态机、双端选型与数据库先立住
2.1 一个上门订单的完整流转:八个状态和一张状态表
先看业务里发生的事,再看代码,不然对着源码你会迷路。一个上门订单从发起到结束,通常涉及五类角色:用户、技师、商家运营、平台客服、系统定时任务。用户负责选项目、选时间、填地址;技师负责查看待接单、抢单或确认派单;商家运营管上架、定价和排班;平台客服兜退款、投诉;定时任务负责超时关闭订单。源码里的权限设计也基本按这五类人来分表。
订单的状态设计决定整个系统的可靠性。常见做法是从 0 开始编号:0 待支付、1 已支付待派单、2 已派单待服务、3 服务中、4 待核销、5 已完成,再预留 99 已取消、98 退款中、97 已退款。状态不能乱跳,「已支付」只能从「待支付」来,不能从「已取消」来;「待核销」只能从「服务中」来。把这些约束写进代码,后面才不会出现一个订单同时在派单又在退款的怪事。
| 事件 | 原状态 | 新状态 | 触发方 |
|---|---|---|---|
| 提交订单 | 无 | 待支付 | 用户 |
| 支付成功 | 待支付 | 已支付待派单 | 支付回调 |
| 派单/抢单 | 已支付待派单 | 已派单待服务 | 运营/技师 |
| 技师开始服务 | 已派单待服务 | 服务中 | 技师 |
| 服务完成确认 | 服务中 | 待核销 | 技师 |
| 用户确认核销 | 待核销 | 已完成 | 用户 |
| 超时未付/用户取消 | 待支付 | 已取消 | 定时任务/用户 |
| 售后申请 | 已完成 | 退款中 | 用户 |
| 退款完成 | 退款中 | 已退款 | 系统 |
这张表不是摆设。你去改源码里的状态判断条件时,先拿它对照一遍,能避免一半以上的逻辑冲突。
2.2 H5与小程序双端:一套业务逻辑、两套适配层
标题里写明了「H5小程序」,这也是同城上门服务的标准形态。常见做法是用一个跨端框架(最多见的是 uni-app)把用户端写成一套代码,同时编译成 H5 页面和小程序包。选这条路的理由很实际:家政按摩用户大多从群里、海报、搜索进来,H5 点链接就能打开,好传播;沉淀用户则靠小程序,顾客用完下次再找也方便。一套业务代码维护两端,比维护两个独立前端省一半功夫。
但「一套代码」不等于没有适配成本。两个端有几处天然不同:登录方式上,小程序用平台提供的 code 向服务端换登录态,H5 通常走手机号验证码;支付上,小程序走平台内支付,H5 走网页支付,两套回调地址不一样;定位权限上,小程序要用户授权地理位置,H5 浏览器是弹窗询问、也容易被拒绝。这个源码包里,这三处一般会拆成独立的适配文件,改的时候不要去动公共订单逻辑,只改适配层。
后端选型上,这类源码最常见的是 PHP 和 Java 两派。PHP 部署轻、教程多,一个人也能维护;Java 严谨、扩展强,但起手重。自己开区域站,PHP 方向够用;团队长期迭代,Java 更稳。文章后面以 PHP 版为例讲参数,思路对 Java 版同样适用。
2.3 数据库先看三张表:订单、技师、服务项目
数据库是一个源码包的底气。我解压任何一套源码,第一件事不是跑页面,而是打开 SQL 文件看数据表。上门预约系统里,订单表、技师表、服务项目表是核心中的核心,字段直接反映业务边界。订单表常见设计如下:
-- 订单表核心字段 CREATE TABLE `order` ( `id` int(11) NOT NULL AUTO_INCREMENT, `order_no` varchar(32) NOT NULL COMMENT '业务订单号,展示给用户', `user_id` int(11) NOT NULL COMMENT '下单用户ID', `service_id` int(11) NOT NULL COMMENT '所选服务项目ID', `technician_id` int(11) DEFAULT '0' COMMENT '技师ID,0表示待派单', `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '订单状态,0待支付', `appoint_time` int(11) NOT NULL COMMENT '预约开始时间,时间戳', `service_duration` int(11) NOT NULL COMMENT '服务时长,单位分钟', `amount` decimal(10,2) NOT NULL COMMENT '订单金额', `pay_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '支付状态,0未付 1已付', `address` varchar(255) NOT NULL COMMENT '上门地址', `lng` decimal(10,6) NOT NULL COMMENT '经度(GCJ02坐标系)', `lat` decimal(10,6) NOT NULL COMMENT '纬度', `cancel_reason` varchar(255) DEFAULT '' COMMENT '取消原因', `create_time` int(11) NOT NULL, `update_time` int(11) NOT NULL, PRIMARY KEY (`id`), KEY `idx_status_create` (`status`, `create_time`), KEY `idx_technician_time` (`technician_id`, `appoint_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;这段 SQL 有三个关键点。第一,order_no 是给用户看的业务单号,自增 id 只在内部用,两者分开,避免用户通过 id 推算出平台一天的订单量。第二,technician_id 默认 0 表示「还没指派」,这是待派单状态的数据依据,不要把它做成非空必填。第三,pay_status 和 status 是两回事:前者管钱,后者管业务流转。判断「已支付待派单」时两个字段要一起看,只看 status 会被退款场景坑到。
技师表和服务项目表相对简单,但有两个字段常被忽略:技师表里要有「可服务范围」或商圈 ID,而不是只存一个工作地址;服务项目表里要有「上门费」和「服务费」两个概念,方便后面做折扣和满减。这些不一定是原版源码自带的,但二次开发时一定要补。下单前查服务项目、下单选技师时段、支付后分配技师,这三条链路全建立在三张表之上,先读懂它们再改代码,省下大量返工时间。
3. 本地跑通上门预约源码:从zip解压到H5页面可见的路径
3.1 环境准备:三个最低版本先过一遍
上手第一步不是双击解压,而是确认环境。这类 PHP 版上门预约源码,最省心的组合是:PHP 7.2 以上(8.0 会遇到部分旧库不兼容)、MySQL 5.7 以上、Node.js 12 以上。先用三条命令检查:
php -v mysql --version node -vPHP 版本不对,我一般用面板类图形工具切版本,比源码编译省时间。MySQL 低于 5.7,SQL 里的索引和字符集设置会不兼容,直接换库更省事。Node 版本太低,前端 npm install 必报错,建议装一个长期支持版本。这三个条件不满足,后面每一步都在踩坑,别跳。
3.2 解压并启动H5前端:四个命令让页面先出来
这类源码包典型结构是 user_h5、user_mp、admin、api、database 几个目录,命名可能略有不同。先启动 H5 的目的很简单:浏览器不受小程序开发者工具限制,能最快确认前端工程没问题。
# 解压到工作目录 unzip ./同城上门家政按摩H5小程序源码\ 上门预约系统.zip -d ./appointment cd ./appointment # 找到H5前端工程目录,源码包里一般是 user_h5 或 h5_app cd user_h5 # 安装依赖,卡住时可以换registry源 npm install # 启动H5本地开发服务,默认端口一般是8080 npm run dev:h5这里每个命令的目的:unzip 解压后进入前端工程目录;npm install 按 package.json 拉依赖;npm run dev:h5 调用跨端框架的 H5 编译脚本,最终在浏览器里打开本地端口看到首页。如果 npm install 期间报权限错误,多数是 node_modules 目录权限问题,删除后重装即可,不要用管理员强行改权限。
浏览器打开能看到页面,说明前端代码没问题。但此时页面大概率拿不到真实数据,因为接口地址还没指向你的后端。这是正常的,下一步配后端。
3.3 初始化数据库与后端:导入SQL并改连接配置
后端目录(api 或 server)里通常有一份 SQL 文件,先把数据库建好:
# 创建数据库后导入初始化数据 mysql -uroot -p -e "CREATE DATABASE IF NOT EXISTS appointment_db DEFAULT CHARSET utf8mb4;" mysql -uroot -p appointment_db < ./database/appointment_db.sql导入完成后,打开后端配置文件(PHP 版常见是 config.php 或 env 文件),把数据库连接改成你本机的:
// api/config.php —— 本类源码包里最常见的核心配置 return [ // 数据库连接:host、库名、账号、密码 'db_host' => '127.0.0.1', 'db_port' => 3306, 'db_name' => 'appointment_db', 'db_user' => 'root', 'db_pass' => '你的数据库密码', // 登录态签名密钥:用于用户/技师token的加签 'app_key' => '请改成一段至少32位的随机字符串', // 小程序AppID与密钥(小程序端联调时要用) 'mini_app_id' => '', 'mini_app_secret' => '', // 支付相关:商户号、密钥和回调地址,上线前再填真实值 'pay_merchant_id' => '', 'pay_key' => '', 'pay_notify_url' => 'https://你的域名/api/order/notify', ];提示:app_key 从本地调试第一天就改成自己的随机字符串,别等到上线再改。上线再换密钥,所有已签发的登录态会全部失效,用户无感知地全被踢下线。
pay 那几个字段现在留空就行,本地联调用不到支付,第 4 章讲回调时再细说真实配置怎么填。
3.4 前端接口地址的配置位置:BASE_URL、AppID与合法域名
改完数据库,还要把前端和后端连起来。H5 前端里,全局请求地址一般在 utils/request.js 或 config.js:
// user_h5/src/utils/request.js —— 统一请求封装 import { BASE_URL } from './config'; export function request(options) { return new Promise((resolve, reject) => { uni.request({ // 本地开发指向本机后端接口 url: BASE_URL + options.url, method: options.method || 'GET', data: options.data || {}, header: { // 登录态通过Authorization头传递 'Authorization': uni.getStorageSync('token') || '' }, success: (res) => { if (res.data.code === 0) { resolve(res.data.data); } else { // 业务码非0时统一走错误提示 uni.showToast({ title: res.data.msg, icon: 'none' }); reject(res.data); } }, fail: reject }); }); }对应的 config.js 里把 BASE_URL 改成后端入口地址。本地开发时,如果你用手机访问 H5 页面,ip 不能填 127.0.0.1,要填电脑在局域网里的 ip,比如 192.168.1.100。这是新手最容易翻车的地方:浏览器里按 F12 看接口请求,凡是 net::ERR_CONNECTION_REFUSED,九成是 BASE_URL 配成了 localhost 而访问端不在同一台机器上。
小程序端则要单独配置:在源码 manifest.json 里填上小程序平台的 AppID;在开发者工具里导入工程后,本地调试可以在「详情-本地设置」勾选「不校验合法域名」,这是调试期的便捷开关,上线前必须关闭并配置真实的 request 合法域名。改完这三处,前端、后端、数据库就串起来了,接下来走一遍核心的下单联调链路。
4. 核心链路联调:下单、支付回调与派单的参数和顺序
4.1 提交预约:前端提交哪些参数,后端先拦哪些问题
跑通登录之后,第一笔真实业务从「提交预约」开始。前端在用户选定服务项目、时间段、上门地址后,组装参数请求创建订单:
// user_h5/src/api/order.js —— 提交预约示例 export function createOrder(data) { return request({ url: '/order/create', method: 'POST', data: { service_id: data.serviceId, // 服务项目ID,来自项目列表 technician_id: data.techId || 0, // 指定技师时传ID,否则0 appoint_time: data.appointTime, // 预约开始时间戳(秒) address: data.address, // 上门地址文本 lng: data.lng, // 经度,GCJ02坐标 lat: data.lat, // 纬度 customer_name: data.name, // 预约人姓名 customer_phone: data.phone, // 预约人手机号 remark: data.remark || '' // 备注,可空 } }); }这里有几个参数背后都有业务判断。appoint_time 为什么用时间戳而不是「2025-06-01 14:00」这样的字符串?因为跨端处理日期字符串容易在时区和格式上各写出 bug,时间戳最无歧义。lng、lat 为什么必须传?后端要靠它判断用户是否在服务范围内,这就是「同城」二字的落点。后端 create 接口至少要做三件事:服务项目是否上架、技师在所选时段是否已有订单、地址是否在可服务范围内。很多问题单,都是这三个校验里少了一个。
4.2 创建订单的后端逻辑:先落待支付,再谈其他
后端创建订单的逻辑,我建议按这个顺序写,也是这类源码里的常见做法:
public function create(array $params) { // 第一步:校验项目是否存在且上架 $service = ServiceModel::where('id', $params['service_id']) ->where('status', 1) ->find(); if (!$service) return $this->fail('该项目暂不可预约'); // 第二步:校验技师档期,锁住技师该时段 if ($params['technician_id'] > 0) { $exist = Order::where('technician_id', $params['technician_id']) ->where('appoint_time', $params['appoint_time']) ->whereIn('status', [1, 2, 3, 4]) // 未支付单不占用档期 ->find(); if ($exist) return $this->fail('该技师该时段已被约,请选择其他时间'); } // 第三步:生成订单,初始状态为0(待支付) $orderData = [ 'order_no' => $this->buildOrderNo(), // 业务单号 'user_id' => $params['user_id'], 'service_id' => $params['service_id'], 'technician_id' => $params['technician_id'] ?: 0, 'status' => 0, 'appoint_time' => $params['appoint_time'], 'amount' => $service['price'], // 金额只在服务端取 'address' => $params['address'], 'lng' => $params['lng'], 'lat' => $params['lat'], 'create_time'=> time(), ]; Order::create($orderData); // 第四步:设置未支付超时关闭时间,交给定时任务处理 // 常见做法:每次建单往缓存里放一个 key,TTL=30分钟 Cache::set('order_timeout_' . $orderData['order_no'], 1, 1800); return $this->ok(['order_no' => $orderData['order_no']]); }这段代码有三点值得说透。第一,金额永远以服务端查到的价格为准,前端传的 price 不能信,否则改一下请求就能 0 元下单。第二,技师档期查询时,status 要排除待支付(0)以及取消、退款状态,因为没付钱的订单不应该占着技师时段;如果把待支付单也算进档期,用户下单后不付钱,技师时段就被白锁一小时,这是影响接单率的常见 bug。第三,超时关闭订单的机制,简单源码通常用定时任务扫表;如果走扫表方案,订单表必须建 (status, create_time) 联合索引,数据一多才不至于拖慢库。
4.3 支付回调与派单:验签、幂等、改状态、再通知的次序
支付整块是最像黑匣子的地方:看不到支付平台内部处理,只能靠回调日志判断发生了什么。回调处理的顺序,决定你半夜会不会被商户投诉吵醒:
// api/controller/NotifyController.php public function payNotify() { // 1. 读取原始回调,先验签 $raw = file_get_contents('php://input'); $data = json_decode($raw, true); if (!$this->verifySign($data)) { return $this->fail('签名错误'); // 支付平台会认为失败并重试 } // 2. 幂等判断:已处理过的回调直接成功返回 $order = Order::where('order_no', $data['order_no'])->find(); if ($order['pay_status'] == 1) { return $this->ok('已处理'); // 重复通知时不要再改状态 } // 3. 事务中更新订单:待支付 -> 已支付待派单 Db::transaction(function () use ($order) { Order::where('id', $order['id']) ->update([ 'status' => 1, // 已支付待派单 'pay_status' => 1, // 支付状态已付 'pay_time' => time(), 'pay_no' => $data['transaction_id'], // 平台交易号 ]); }); // 4. 事务提交后再触发派单或通知技师 // 指定了技师就直接推送接单通知; // 未指定则进入派单池等待分配 $this->dispatchOrder($order['id']); // 5. 返回成功,停止回调重试 return $this->ok('success'); }验签、幂等、更新、派单,这个顺序一个都不能颠倒。验签不过直接返回失败,让平台重试;幂等判断保证同一笔回调来十次也不会把状态改乱;事务里只做状态更新、不做业务通知,是因为通知技师是外部动作,和数据库更新放在同一个事务里,一旦通知失败可能导致整个事务回滚,订单状态反而没了。事务提交后再派单,哪怕派单逻辑崩了,订单也已经处于「已支付待派单」,后台还能手动补派。
派单环节还要防并发。多个技师同时抢一个订单时,如果只判断「订单当前是否已派单」而不加锁,两个请求可能同时通过判断、同时改状态,最后出现一个订单两个技师。常见解法是用条件更新:UPDATE order SET technician_id=?, status=2 WHERE id=? AND status=1,然后看影响行数,只有行数为 1 的技师算抢单成功。这条 SQL 能防住 90% 的并发抢单问题,剩下 10% 等订单量大了再上 Redis 锁。
5. 上门预约系统常见问题排查:5个让新手翻车的现场与解法
先说一句血泪经验:下面 5 个问题我基本都见过真实案例,共同根源是「把本地 Demo 的跑通当成了线上可用」。每条按现象、原因、解决来记,能帮你少踩一半坑。
5.1 H5页面白屏或接口拒绝连接,九成是地址配错
现象:浏览器打开 H5 页面白屏,F12 里接口请求全是 net::ERR_CONNECTION_REFUSED。
原因:BASE_URL 里配的是 127.0.0.1,但访问页面的手机或另一台电脑访问不到本机;或者后端服务根本没启动,站点运行目录没指到后端的 public 目录。解决:本机调试时把 BASE_URL 换成电脑局域网 IP;再确认站点配置里 root 指向正确的运行目录、伪静态已开启,否则所有非根路径都会 404,表现上和接口被拒一样。真遇到玄学样式错乱,再看是不是前端静态资源路径用了绝对地址。
5.2 小程序登录失败,后端报code无效或openid为空
现象:小程序端登录按钮转圈后失败,后端日志出现「code 无效」或「openid 获取失败」。
原因:大多是小程序 AppID 和后端 mini_app_id 不一致,或者 AppSecret 填错;也可能是调试期没勾选「不校验合法域名」,登录接口在工具层就被拦了。排查时先看后端有没有收到 code,收到后返回的报错是什么:如果 code 都没到后端,是请求没发出来;到了但换 openid 失败,基本是 AppID/Secret 的问题。解决:把后端 mini_app_id、mini_app_secret 改成当前小程序账号的;调试期在开发者工具里勾选不校验域名,上线前换回真实域名。
5.3 支付回调重复通知,订单状态被改乱
现象:一笔订单支付成功后,状态从「已支付待派单」一路被改成「已派单」「服务中」,技师端还没接单,状态却像坐了火箭。
原因:回调处理没做幂等。支付平台对未确认的回调会重试多次,每次进来都执行一遍状态流转更新,状态机就被推过了头。解决:回调处理第一件事是查订单当前 pay_status,已支付直接返回成功;状态更新要带条件,只有待支付状态才能更新为已支付。这就是「后悔药」,加上之后回调重试多少遍都安全。
5.4 同城范围判断不准,用户明明在附近却提示超出范围
现象:用户定位就在服务城市核心区,下单时却提示「超出可预约范围」。
原因:坐标口径不一致。前端拿到的是 GPS 原始坐标,后端判断范围用的服务商坐标是另一种坐标系,两者偏移几百米到几公里,范围计算自然不准。解决:前端取到定位后统一转成 GCJ02 坐标再传给后端,后端距离计算也用 GCJ02;范围判断不要只用「直线距离小于固定值」这一条硬条件,结合用户手动选择的区域 ID 一起判断,边缘误判会少很多。验证方法很简单:拿同一个地址,分别打印原始坐标和转换后坐标,看距离差了多少。
5.5 订单卡在待支付,用户却说已经付了款
现象:后台一堆待支付订单,用户端却显示「已支付」,两边对不上。
原因:支付回调地址不可达。本地调试时回调配成了 localhost,支付平台根本调不到;或者服务器防火墙拦了支付平台 IP。另一个常见原因是超时未付的定时任务没配 crontab,该自动关闭的待支付单一直躺着,用户一看「我还等着呢」体验极差。解决:先到支付平台商户后台查回调记录,确认回调 URL 可访问;然后给服务器配上任务计划,把超时未付的订单自动关掉,日志重定向到文件方便回查。这个问题是典型的上线后头一天才暴露的翻车现场,因为开发环境回调根本走不通,开发时反而没察觉。
6. 从Demo到上线:补上定时任务、安全与配置这最后三公里
6.1 上线前必须过一遍的检查清单
| 检查项 | 本地要求 | 线上要求 |
|---|---|---|
| app_key/签名密钥 | 随机长字符串 | 与本地不同,妥善保管 |
| 数据库连接 | 本机账号 | 内网或最小权限账号 |
| 接口BASE_URL | 局域网IP | 已备案HTTPS域名 |
| 小程序AppID/Secret | 测试号 | 真实主体账号 |
| 支付密钥/证书 | 可留空 | 正式商户号 |
| 定时任务 | 手动执行 | crontab已配置 |
6.2 超时订单定时任务:这一行配置不上线必翻车
很多这类源码自带超时未支付自动取消的命令行脚本,但它不会自己跑,要你配进系统任务计划:
# crontab -e 添加以下两行 # 每30分钟处理一次超时未支付订单 */30 * * * * cd /data/www/appointment/api && php think order:timeout >> /data/www/logs/order_timeout.log 2>&1 # 每天凌晨1点清理三天前的日志文件 0 1 * * * find /data/www/logs -name "*.log" -mtime +3 -delete第一行里的 order:timeout 是源码自带的命令名,具体以php think list查到为准。日志重定向不能省,不重定向时 crontab 执行出错你看不到任何错误,排查只能干瞪眼。第二行顺手做日志清理,小项目最容易拖垮磁盘的就是无限增长的日志文件。
6.3 我一个上线前习惯:本地先走一遍完整退款链路
最后说一个我自己的习惯:每次上线前,除了测试下单支付,一定还会在测试环境走一遍「下单-支付-申请退款-退款到账」的完整链路。特别是退款接口里对已派单订单的拦截逻辑,如果漏了,技师已经在路上的订单被退款成功,才是真·现场翻车。这类源码的退款多半是后台管理员手动发起,你要确认后台退款操作会检查订单状态,而不是无条件打款。这个习惯帮我拦下过几次事故,也推荐你从第一个项目就养成。希望帮到你。
本文还有配套的精品资源,点击获取