维修行业一直有个老问题:客户不知道师傅几点有空、修一次要等多久,师傅这边也不知道当天会有几单活,两头全靠电话和微信来回拉扯。我前阵子正好用 Python 加 uniapp 给朋友的维修门店做了一套微信小程序预约维修系统,把用户端提交预约、师傅端接单、时间槽管理、订单状态跟踪这一整套流程完整跑通,顺手也解决了高峰期单子撞车的问题。这篇就把整个项目从后端表结构设计、uniapp 前端适配、到小程序审核上架的完整过程摊开讲,适合想自己动手做同类型 O2O 预约项目的开发者参考,也适合刚接触小程序开发、想拿一个真实业务练手的人跟一遍。
1. 项目定位与功能拆解:预约维修系统到底要解决什么问题
动手写代码之前,我先花了两天把需求理清楚。预约维修系统听着简单,但实际上是典型的双边服务平台:一边是发起预约的客户,一边是承接订单的维修师傅,系统要同时服务好两端,还要处理预约时间、订单状态这类容易出 bug 的业务逻辑。
1.1 两类用户的核心诉求
客户端的痛点很直接:不知道师傅几点有空,不知道自己的手机修到什么程度了,也不想为了一个维修时间反复打电话确认。所以用户端必须提供清晰的预约入口、可选的维修项目和时间段,以及随时能看到的订单进度。
师傅端的诉求更偏效率:希望当天的工作安排一目了然,新单能及时提醒,修完一单能快速流转到下一单。另外师傅还希望能自己维护可接单的时间段,比如周三下午有事就关掉这段时间,避免接单后放鸽子。
这两个诉求合在一起,就决定了系统必须有一个核心能力:时间槽(time slot)管理。用户只能预约师傅开放的时间段,师傅端能灵活调整自己的可预约安排。这是整个系统最关键的逻辑,后面我会专门讲。
1.2 功能模块清单与优先级
我按对业务的重要性把功能分了三档,先做核心,再补外围:
| 模块 | 优先级 | 说明 |
|---|---|---|
| 用户登录与身份绑定 | P0 | 微信授权登录,绑定用户 openid |
| 维修项目展示 | P0 | 展示维修类型、预估价格、所需时长 |
| 预约下单 | P0 | 选择师傅、选择时间段、填写故障描述 |
| 时间槽管理 | P0 | 师傅端维护可预约时间段,用户端查询余量 |
| 订单状态流转 | P0 | 待接单、已接单、维修中、已完成、已取消 |
| 订阅消息通知 | P1 | 接单、进度更新时给用户发微信订阅消息 |
| 取消与改约 | P1 | 支持用户取消、师傅改约,需处理时间释放 |
| 维修评价 | P2 | 完成后评价,沉淀店铺口碑 |
P0 级别是系统能跑起来的最小闭环,P1 是提升体验的加分项,P2 可以等上线后再迭代。这样划分能让开发节奏很清楚,不会一开始就被各种小需求拖住。
2. Python 后端:框架选型与数据库建模
后端我选了 Python 技术栈,这是整个系统的大脑。前端小程序不管怎么换,只要后端接口稳定,业务逻辑就不容易乱套。
2.1 为什么选 Flask 而不是 Django
Python 后端主流就是 Flask 和 Django 两个选择。预约维修系统这种业务,接口数量大概在二十个左右,数据模型不是特别复杂,更看重快速开发和接口的灵活性,所以我选了 Flask。
Flask 轻量,路由和请求处理直观,适合中小型项目快速迭代。Django 自带 Admin 后台和 ORM,功能全,但框架约束多,学习成本和项目初始体量都偏大。当然如果你团队本来就很熟 Django,用 Django 做这个项目也完全没问题,核心逻辑是一致的。
配套组件方面,我用了 Flask-RESTful 来组织接口,Flask-SQLAlchemy 做 ORM,数据库先用 SQLite 做开发,部署时换 MySQL。考虑到预约系统并发量不会特别夸张,MySQL 加合理索引就足够扛了。
2.2 核心表结构设计
数据库是预约系统的地基,表设计直接决定后面业务逻辑好不好写。我设计了五张核心表:用户表、师傅表、维修项目表、订单表、时间槽表。
用户表主要存微信身份信息和联系方式:
CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, openid VARCHAR(64) UNIQUE NOT NULL, nickname VARCHAR(64), avatar_url VARCHAR(255), phone VARCHAR(20), created_at DATETIME DEFAULT CURRENT_TIMESTAMP );师傅表除了基本信息,还维护一个服务状态字段,标记当前是否可接单:
CREATE TABLE technician ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(64) NOT NULL, phone VARCHAR(20) NOT NULL, avatar_url VARCHAR(255), skill_tags VARCHAR(255), status TINYINT DEFAULT 1, -- 1可接单 0休息 created_at DATETIME DEFAULT CURRENT_TIMESTAMP );订单表是核心表,状态字段的取值设计要非常谨慎,我在下一章专门讲。时间槽表则用来记录师傅每一天的可预约时间段和当前已被预约的数量。
2.3 接口设计与鉴权方案
接口统一走 RESTful 风格,比如POST /api/order/create创建预约、GET /api/order/detail查订单详情、PUT /api/order/status更新订单状态。请求和响应都用 JSON,字段命名统一用小驼峰,前端 uniapp 解析起来省事。
鉴权我用的是 JWT。微信小程序端拿到code后,后端调微信接口换openid,然后签发一个 JWT 给小程序,后续请求都在 Header 里带Authorization: Bearer <token>。这里有一个很容易踩的坑:JWT 过期时间不能设太长,我设的是 7 天,用户长期不打开小程序导致 token 过期时,前端要能静默重新登录,不能直接弹错误提示把用户卡住。
3. uniapp 前端:一套代码跑通微信小程序的适配细节
前端我选了 uniapp 框架,最大的好处是一套 Vue 代码可以编译到微信小程序。开发调试的时候还能在 H5 端快速预览,不用每次都在微信开发者工具里刷。
3.1 工程结构与条件编译
uniapp 项目结构上有几个关键目录要分清:pages放页面,components放公共组件,api放请求封装,utils放工具函数。预约业务涉及的页面我拆成了首页、维修项目列表、预约下单页、订单列表页、订单详情页、我的页面,一共六个主页面。
条件编译是 uniapp 适配多端的核心机制。比如微信小程序的wx.login只能在微信环境跑,我这样写:
// #ifdef MP-WEIXIN const code = await new Promise((resolve, reject) => { uni.login({ provider: 'weixin', success: res => resolve(res.code), fail: reject }) }); // #endifMP-WEIXIN这个条件编译标记保证了这段代码只在小程序平台编译,H5 端不受影响。实际开发中我总结的经验是:业务逻辑尽量放在公共代码里,只有真正平台相关的能力(登录、支付、订阅消息)才用条件编译隔离。
3.2 微信登录与用户身份打通
微信小程序的登录流程和普通网页登录差别很大,核心是拿到code换openid。前端调uni.login拿到临时code,传给后端的登录接口,后端用code换openid,再查用户表,没注册过就自动创建。
这里要注意一个体验细节:首次登录时不能只给用户一个"登录成功"的提示就完事,还要引导用户补充手机号。因为后续师傅联系用户要打电话,没有手机号预约流程根本走不下去。我用的是uni.chooseAddress类似的能力之外,更推荐直接用微信的getPhoneNumber按钮组件,用户点一下授权就能拿到手机号,体验比手动输入流畅得多。
3.3 表单提交、订单列表与订阅消息
预约下单页是整个前端最复杂的页面。用户要选维修项目、选师傅、选时间段、填故障描述,四个步骤的信息要串起来。项目、师傅、时间段这三个数据是联动关系:选了项目之后,系统要筛选出能修这个项目的师傅;选了师傅之后,时间段列表要显示这个师傅当天还有哪些空档。
订单列表和详情页相对简单,主要是状态展示。这里我犯过一个低级错误:前端直接用数字状态值去判断显示什么文案,后来后端把状态枚举加了一个新值,前端就漏了一处展示逻辑。正确做法是前端维护一份状态映射表,新增状态时改映射表就行,不会牵一发动全身。
订阅消息这块,小程序和公众号的逻辑完全不同。用户必须主动订阅一次,小程序才能在后续某个时机给用户发一次消息,而且一次性订阅消息只能用一次。我的做法是:在用户提交预约成功后弹订阅请求,引导用户订阅"接单通知",这样师傅接单时用户就能收到提醒。这里要提前跟微信官方申请模板,审核也需要时间,建议项目一开始就申请。
4. 最容易翻车的核心逻辑:时间槽冲突与订单状态机
预约系统做得再多功能,核心还是一件事:同一个时间段,不能有两个用户约同一个师傅。这块逻辑我重写了两遍才稳定,值得单独拿出来讲。
4.1 时间槽预占的两段式设计
第一版我图省事,用户提交预约就直接把时间段标记为已占用。结果一上线就出问题:用户提交了预约但是没支付,或者因为网络问题反复提交,师傅的时间段就被假订单占满了。
后来我改成两段式设计:用户提交预约时,时间槽进入"预占"状态并生成订单;师傅接单(或系统自动确认)后,时间槽才变成"已确认"。预占的订单如果超过 30 分钟师傅没接单,系统自动释放时间槽,用户端看到的就是订单超时取消。这样既避免了假订单占坑,又给了师傅合理的响应时间。
时间槽表的关键字段是:师傅 ID、日期、开始时间、结束时间、总容量、已占数量。每次预约都要做一次原子更新:
UPDATE time_slot SET booked_count = booked_count + 1 WHERE technician_id = ? AND slot_date = ? AND start_time = ? AND booked_count < capacity;通过判断affected_rows是否等于 1,就能确定预约是否成功。这条UPDATE语句自带行锁,天然避免并发情况下两个用户同时抢到最后一个名额,比先查再更的写法安全得多。
4.2 订单状态机的流转设计
订单状态的取值不能随意加,要在设计阶段就把流转路径定死。我最终用的是六个状态:
待接单 -> 已接单 -> 维修中 -> 已完成 | | | v v v 已取消 已取消 已取消每个状态能跳转到哪些状态,在代码里用配置表约束,不允许随意跳转:
ORDER_STATUS_TRANSITIONS = { 'pending': ['confirmed', 'cancelled'], 'confirmed': ['repairing', 'cancelled'], 'repairing': ['completed', 'cancelled'], 'completed': [] }这样做的好处是,前端无论怎么调接口,订单状态都不会出现"已完成又变成维修中"这类逻辑错误。接口层再做一层校验,不符合流转规则的更新请求直接拒绝。
4.3 取消、超时、拒单的边界情况
边界情况是这类系统最容易出 bug 的地方。我把常见的都列出来逐个处理:
- 用户取消待接单的订单:释放时间槽,直接成功。
- 用户取消已接单的订单:师傅端要收到通知,时间槽释放,但需要和师傅确认是否存在上门费用。
- 师傅拒单:状态改为已取消,时间槽释放,用户端提示重新预约。
- 超时未接单:定时任务扫描超过 30 分钟还在"待接单"状态的订单,自动取消并释放时间槽。
定时任务我用的是APScheduler,每两分钟跑一次扫描。第一次上线时把扫描间隔设成了 10 分钟,结果测试时发现用户等太久了,后来改成 2 分钟,体感好了很多。这个参数要结合业务量调整,别照抄别人的配置。
5. 部署上线与微信审核的实际踩坑记录
后端代码写完只是第一步,真正让项目跑起来,部署和审核环节踩的坑一点不比写代码少。
5.1 服务器、域名与 HTTPS
微信小程序有个硬性要求:所有请求的接口域名必须备案,而且必须走 HTTPS。这意味着我不仅要买一台云服务器,还要给域名做备案,再申请 SSL 证书配好 HTTPS。
我用的是 Nginx 做反向代理,把https://api.example.com转发到本机的 Flask 服务。这里提醒一下,小程序的 request 合法域名配置只能在微信公众平台后台添加,一天只能修改五次,调试的时候要省着用。
Nginx 配置里有两个容易忽略的点:一是client_max_body_size默认只有 1MB,用户上传维修照片稍微大点就报 413;二是给 API 接口单独开日志,排查问题的时候非常有用。
5.2 图片上传与存储方案
预约维修场景里,用户经常要拍故障部位的照片,师傅也要上传维修前后的对比图。图片处理看起来简单,实际要考虑存储位置和访问速度。
我一开始把图片直接存在服务器本地磁盘,后来发现两个问题:一是图片多了之后备份很麻烦,二是 Nginx 得额外配静态文件访问路径。后来还是切到了对象存储方案,上传接口返回图片 URL,小程序端直接展示。如果没有预算买云服务,用本机存储加 Nginx 静态目录也能跑,但一定要把上传目录和数据目录分开备份。
5.3 审核被拒的典型问题
小程序审核是整个流程里最磨人的环节。我提交审核被拒过两次,第一次是因为隐私政策问题,小程序收集用户手机号必须有明确的隐私保护指引,而且要在小程序内能查到完整的隐私协议。第二次是因为用户协议里出现了"最终解释权归商家所有"这种表述,被判定为霸王条款,改掉之后才过。
另外提醒一点,小程序里所有涉及用户信息的页面,都要有对应的隐私授权弹窗。微信平台现在对用户隐私保护查得比较严,哪怕只是收集头像昵称,也要在隐私协议里写清楚用途。这块建议提前看官方文档,别等审核被拒了再补。
6. 真机实测与后续扩展建议
部署完成之后,不等于项目就结束了。我用两台手机做了完整的真机测试,把用户端和师傅端的操作串起来跑了几遍,又根据测试结果优化了一些体验细节。
6.1 关键场景测试清单
我整理了一份测试清单,照着跑能覆盖大部分核心场景:
| 测试场景 | 操作步骤 | 预期结果 |
|---|---|---|
| 新用户首次预约 | 微信打开小程序、选择项目、选时段、提交 | 订单进入待接单,用户收到订阅消息 |
| 并发抢占最后一个时段 | 两台手机同时提交同一时段 | 一台成功,一台提示时段已满 |
| 师傅接单后用户查看进度 | 师傅端点接单,用户端刷新订单详情 | 状态变为已接单,时间槽确认占用 |
| 用户取消待接单订单 | 订单详情页点取消 | 状态变为已取消,时间槽释放 |
| 超时订单自动取消 | 提交订单后等待 30 分钟 | 订单自动取消,时间段释放 |
| 手机号授权拒绝 | 用户拒绝授权手机号 | 提示需补充手机号才可提交 |
真机测试发现最值得优化的地方是下单页的加载速度。列表页一次性返回了未来 7 天所有师傅的时间槽数据,数据量大导致首屏卡顿。后来改成按天查询,用户切换日期时再请求当天数据,体感立刻不一样了。这也是预约类项目的通用优化思路:数据按需加载,别贪多。
6.2 可以继续扩展的方向
预约维修系统的核心闭环已经跑通,但如果要做成真正能持续运营的产品,还有几个方向值得投入:
- 支付闭环:接入微信支付,支持预约时预付定金、维修完成后支付尾款,能有效减少放鸽子概率。
- 师傅派单模式:目前是用户选师傅,也可以改成用户下单后由系统按距离、评分、忙闲程度智能派单。
- 多门店支持:在用户表和技师表之间加一层门店关系,支持一个品牌多个维修网点共同运作。
- 数据看板:统计每天的订单量、接单率、平均维修时长,给门店经营提供决策依据。
其中支付闭环的优先级最高,因为它直接关系到业务的真实收益。接入微信支付时注意,小程序支付需要先开通微信支付商户号,而且支付回调要处理好幂等性——同一个支付结果通知可能收到多次,后端必须保证只处理一次。
做这个项目的过程中我最大的感受是:预约类系统的难点从来不在页面多漂亮,而在于业务状态的严谨性。时间槽的一行 SQL、订单状态的一张流转表、边界情况的处理逻辑,这些才是真正决定项目能不能稳定跑下去的东西。如果你也在做类似的项目,建议优先把这一层想透,再谈功能丰富度。真到上线之后,你会发现省下的排查时间比写代码的时间还多。