去年接手了一个手机维修连锁店的数字化改造项目,他们最大的痛点就是线下排队混乱、订单跟踪困难。我们用Python+Uniapp开发了一套微信小程序预约系统,上线三个月后门店效率提升了40%。这种前后端分离的跨平台方案特别适合中小型服务类企业,今天就把核心实现思路和踩坑经验分享给大家。
这个系统本质上是个O2O服务预约平台,但针对手机维修场景做了深度定制。用户端小程序提供从故障诊断到售后评价的全流程服务,维修端Web后台实现智能派单和进度管理。技术栈选择上,前端用Uniapp实现"一次开发多端发布",后端用Python Flask保持轻量灵活,数据库用MySQL保证事务安全。
提示:选择Flask而非Django的主要考虑是维修业务逻辑相对简单但接口变化频繁,Flask的轻量级特性更利于快速迭代。如果是超大型连锁品牌,建议改用Django REST framework。
采用经典的三层架构模式,但针对移动端特性做了优化:
code复制微信小程序(Uniapp) → API网关(Nginx) → 业务逻辑层(Flask)
↘ 消息队列(RabbitMQ) → 异步任务(Celery)
↘ 数据存储(MySQL + Redis)
前端使用Uniapp的vue语法开发,编译后同时发布到微信小程序和H5。特别要注意的是小程序端的生命周期适配,我们在onLaunch中增加了网络类型检测,在弱网环境下自动降级功能。
后端采用Blueprint模块化设计,主要分为:
数据库选型分析:
| 需求 | MySQL方案 | SQLite方案 |
|---|---|---|
| 并发性能 | 支持200+TPS | 仅支持50TPS |
| 事务完整性 | 完整ACID支持 | 部分支持 |
| 运维复杂度 | 需要单独部署 | 零配置 |
| 适合场景 | 多门店连锁 | 单店本地化部署 |
最终选择MySQL 8.0,主要考虑到:
小程序端关键代码示例:
javascript复制// 获取微信code
uni.login({
provider: 'weixin',
success: (res) => {
this.wxCode = res.code
this.getUserProfile()
}
})
// 获取用户信息
getUserProfile() {
uni.getUserProfile({
desc: '用于完善会员资料',
success: (infoRes) => {
this.loginWithCode(infoRes)
}
})
}
后端处理逻辑要点:
踩坑记录:微信的code有效期仅5分钟且单次有效,遇到过因网络抖动导致重复使用code的报错,解决方案是引入Redis做code使用状态缓存。
订单状态流转是系统的核心业务逻辑,我们采用状态模式实现:
python复制class OrderStatus:
@abstractmethod
def cancel(self, order): pass
class PendingStatus(OrderStatus):
def cancel(self, order):
order.status = 'CANCELLED'
order.refund_amount = order.deposit
send_cancel_notification(order.user)
class ProcessingStatus(OrderStatus):
def cancel(self, order):
if order.technician_confirmed:
raise BizException('技师已开始维修,不能取消')
order.status = 'CANCELLED'
order.refund_amount = order.deposit - 20 # 扣除违约金
状态转换规则:
code复制待支付 → (支付超时) → 已取消
待支付 → (支付成功) → 待接单 → (技师接单) → 处理中
处理中 → (维修完成) → 待评价 → (用户评价) → 已完成
春节前促销期间遇到瞬时预约量激增的问题,通过以下方案解决:
python复制def reserve_time_slot(slot_id):
# 使用乐观锁避免超卖
affected = db.session.execute(
"UPDATE time_slots SET quota = quota - 1 "
"WHERE id = :id AND quota > 0",
{"id": slot_id}
).rowcount
if not affected:
raise BizError("该时段已约满")
python复制SQLALCHEMY_BINDS = {
'master': 'mysql+pymysql://master@3306/repair',
'slave1': 'mysql+pymysql://slave1@3306/repair',
'slave2': 'mysql+pymysql://slave2@3306/repair'
}
@app.route('/api/shops')
def get_shops():
# 读操作自动路由到从库
shops = db.session.execute(current_app.config['SQLALCHEMY_BINDS']['slave1'])
return jsonify(shops)
Uniapp编译后的小程序包从3.2MB降到1.6MB的关键措施:
webpack-bundle-analyzer分析依赖javascript复制// 替换 import Vant from 'vant'
import Button from 'vant/lib/button'
import Toast from 'vant/lib/toast'
javascript复制const RepairDetail = () => import('@/pages/repair/detail')
现象:生产环境偶发"签名验证失败"错误
排查过程:
解决方案:
nginx复制location /api/payment {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://backend;
}
现象:安卓设备获取的坐标偏移500米+
原因:国内地图坐标系与WGS84的加密偏差
处理方案:
javascript复制// 坐标转换
import * as coordtransform from 'coordtransform'
function getActualLocation(lng, lat) {
// 微信返回的GCJ02转WGS84
if (isInChina(lng, lat)) {
return coordtransform.gcj02towgs84(lng, lat)
}
return [lng, lat]
}
基于维修类型和技师技能的匹配方案:
python复制def dispatch_order(order):
techs = Technician.query.filter_by(
skill=order.repair_type,
status='IDLE'
).order_by(
Technician.rating.desc(),
func.ST_Distance(
Technician.location,
order.location
)
).limit(5).all()
if not techs:
raise NoTechnicianAvailable()
return techs[0].assign_order(order)
用决策树实现初步故障判断:
python复制from sklearn.tree import DecisionTreeClassifier
clf = DecisionTreeClassifier(
max_depth=4,
criterion='entropy'
)
# 训练数据示例
X_train = [
[1, 0, 1], # [屏幕问题, 进水, 无法开机]
[0, 1, 0] # [电池问题, 未进水, 能开机]
]
y_train = ['屏幕总成', '电池更换']
clf.fit(X_train, y_train)
实际项目中我们还接入了NLP模型处理用户自由文本描述,准确率提升到78%。
这套系统经过三个版本的迭代已经相当稳定,核心经验是:前期做好微信生态的兼容性测试,订单状态变更必须加分布式锁,支付相关操作务必实现幂等性。对于想尝试类似项目的开发者,建议先从单店版做起,逐步扩展连锁功能。