ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Flask+Vue家政保洁预约系统:角色权限与订单状态机实战

2026/9/30 10:58:26 拓冰建站 浏览量
Flask+Vue家政保洁预约系统:角色权限与订单状态机实战 做家政保洁预约系统一开始我以为就是把“用户下单、师傅接单”这两件事串起来就完事了。真把需求理清之后才发现这里面的角色远比想象中多用户要看价格、要选时段保洁员要接单、要上传完工照片老板要排班、要核销、还要看月底营收甚至平台管理员还得处理投诉和退款。如果只按“用户/管理员”两角色去做后面大概率会改到崩溃。所以我决定用 Python Flask 提供后端接口前端交给 Vue 做页面交互把所有角色、权限、订单状态一次性在设计阶段理清楚。这篇文章就把这套系统的拆解思路、关键模块实现、以及我在实际开发中踩过的坑完整分享出来给想自己做类似预约系统的朋友一个可以直接参考的底稿。1. 项目背景与角色拆解家政保洁预约为什么需要“角色多”1.1 一个预约系统的利益相关者有哪些很多教程里的“预约系统”都是单角色示例用户注册选服务下单管理员在后台看到订单。但家政保洁的业务链路要长得多。真实场景里至少存在四类参与者普通用户C端客户需要浏览服务项目、查看价格、选择上门时间、在线支付或线下支付、查看订单进度、提交服务评价。保洁员/服务人员B端执行者需要查看分配给自己的任务、确认接单、导航到客户地址、提交开始/完工状态、上传服务前后对比照片。老板/门店管理员运营方需要维护服务项目和价格、管理保洁员排班、人工派单或系统派单、处理用户投诉、统计每日/每周/每月营收。平台超级管理员如果是多门店或平台化运营还需要管理门店、审核保洁员资质、查看全局数据、处理退款纠纷。这还不算系统本身的后台运维角色。把这些角色全部列出来后你会发现“角色多”不是功能堆叠而是业务权限边界必须清晰。否则用户能看到保洁员手机号、保洁员能改价格、管理员能删评价整个系统就乱了。因此第一步不是写代码而是画一张权限矩阵。1.2 角色权限模型怎么设计才不乱我习惯用一个简单的“角色-权限”表来梳理。家政预约系统的权限核心是按操作对象区分服务项目、订单、用户信息、保洁员信息、评价、统计报表。操作普通用户保洁员门店管理员超级管理员浏览服务项目允许允许允许允许创建预约订单允许禁止可代客下单禁止修改订单时间允许限未接单禁止允许允许接单/拒单禁止允许允许派单禁止提交完工禁止允许允许允许审核退款禁止禁止允许允许管理服务价格禁止禁止允许允许查看营收统计禁止仅个人本店数据全平台实际开发中我并没有引入复杂的 RBAC 权限框架因为角色固定、数量少直接用一张user_role字段加装饰器就能搞定。Flask 里写一个require_role(admin)的装饰器比引入一堆权限依赖更可控。Vue 前端则用路由守卫判断用户角色把不该看到的菜单直接隐藏减少无效请求。角色多意味着数据隔离也要同步考虑。保洁员只能查到分配给自己的订单管理员可以查自己门店的订单用户只能查自己的订单。所有查询都要默认带上角色维度的过滤条件这个习惯越早养成后面越不容易越权。2. 技术选型思路为什么是 Python Flask Vue2.1 Flask 做后端接口的取舍Flask 在预约类项目里的优势是轻、灵活、上手快。家政预约系统并不需要复杂的微服务拆分也不需要大数据流处理核心就是 RESTful API用户登录、获取服务列表、创建订单、更新订单状态、上传图片、查询统计。我选 Flask 还有几个实际考虑开发速度极快一个app.py就能把路由、模型、扩展全部组织起来适合两个人两周内做出可演示版本。配合 SQLAlchemy 操作 SQLite 或 MySQL模型定义直观订单表、用户表、服务表的关联关系非常清晰。Python 生态里有现成的 JWT 库PyJWT 或 flask-jwt-extended和 Flask-CORS前后端联调省事。需要说明的是Flask 默认的开发服务器性能一般生产环境要换 Gunicorn 或 Waitress。但作为本地部署、小规模门店使用的系统Flask 完全够用。如果担心性能可以在接口层增加 Redis 缓存热门服务列表但初期不建议过度设计。2.2 Vue 前端与前后端分离的底气前端选 Vue主要是因为 Vue 的中文资料多、生态稳定、组件化写起来舒服。家政预约系统的页面不算特别复杂但状态多订单状态从“待支付”到“待派单”到“已接单”再到“服务中”“已完成”“已取消”不同状态下按钮和文案都不一样。Vue 的响应式数据绑定和计算属性天然适合处理这类状态切换。我实际用的是 Vue 2 Vue Router Vuex现在新项目可以上 Vue 3 Pinia但核心思路一致。页面划分为用户端首页服务列表、下单页、订单列表、订单详情、个人中心。保洁员端待接单列表、已接订单、工作台开始/完工/上传照片。管理后台服务管理、人员管理、订单管理、数据概览。前后端分离的好处是保洁员端可以单独打包部署成手机浏览器可访问的页面管理员后台在电脑上打开用户端做成响应式适配手机。三套界面共用同一套后端接口接口压力也不大。2.3 数据库选型和表结构规划数据库我推荐 SQLite 起步数据量到几万订单后再平滑迁移到 MySQL。表结构是这个系统的地基规划时我建议至少包含以下核心表user用户表字段包括 id、username、password_hash、roleuser/cleaner/admin/super_admin、phone、avatar、created_at。service服务项目表字段包括 id、name、description、price、duration_minutes、category、status上架/下架。order订单表字段包括 id、order_no、user_id、cleaner_id、service_id、appointment_time、address、status、remark、total_price、payment_status、created_at、updated_at。order_status_log订单状态日志表记录每一步状态变更的操作人、时间、备注。review评价表关联订单、用户、评分、内容、图片。cleaner_schedule保洁员排班表如果需求复杂可以做初期也可以直接在订单表里用 cleaner_id 空置表示未派单。订单表是核心状态字段建议用字符串类型存英文状态码比如pending_payment、pending_assign、assigned、in_service、completed、cancelled、refunded。不要存数字因为别人看代码时很难一眼知道3代表什么。状态日志表很多人会忽略但一旦出现纠纷这张表就是事实依据一定要保留。3. 核心功能落地从下单到接单再到完工3.1 用户端预约下单流程用户端下单流程看起来简单但细节不少。首页展示服务项目时需要从后端接口/api/services获取上下架状态为“上架”的服务。用户点击“立即预约”后进入下单页选择上门日期和时间段。这里有一个关键点保洁员的可约时段校验。家政预约不能只让用户随便选时间否则会出现五个用户同时选了一个保洁员的同一个时间段。我在后端实现了一个简单的时间冲突校验查询该时段内已分配该保洁员且状态不是“已取消”的订单数量达到排班上限就提示“该时段已被约满”。初期用 SQL 查订单表即可conflict_count Order.query.filter( Order.cleaner_id cleaner_id, Order.appointment_time appointment_time, Order.status.in_([assigned, in_service, pending_assign]) ).count()下单创建订单时用Flask-SQLAlchemy的 session 提交同时生成唯一的order_no我习惯用日期加随机数order_no datetime.now().strftime(%Y%m%d%H%M%S) str(random.randint(1000, 9999))支付环节如果不想接入支付宝/微信支付可以先做成“线下支付/到店支付”模式订单里保留payment_status字段管理员在后台手动标记已收款。项目演示和本地部署阶段这种轻量做法非常实用。3.2 保洁员接单与状态流转保洁员登录后看到的是分配给自己的任务列表。这里存在两种派单模式管理员人工派单和系统自动派单。人工派单逻辑简单管理员在订单详情页选择保洁员并点击“分配”后端更新订单的cleaner_id和状态。自动派单则要看保洁员的排班和当前未完成订单量初期可以不做先用人工派单验证流程。保洁员在接单后可以点击“开始服务”把状态从assigned改为in_service服务完成后再点击“完工”上传照片状态变为completed。这里的核心是状态流转的合法性控制。用户不能把一个pending_assign的订单直接改成completed后端必须在每次状态更新时做校验。我在 Flask 里写了状态转移白名单allowed_transitions { pending_payment: [pending_assign, cancelled], pending_assign: [assigned, cancelled], assigned: [in_service, cancelled], in_service: [completed], completed: [], cancelled: [] }只有在白名单里的转移才是合法的否则直接返回 400。这个设计在早期能挡住绝大多数越权操作和误操作。3.3 管理后台的订单调度和统计管理后台是运营者的工作台我要重点说三个页面订单管理页需要支持按状态、时间范围、保洁员、用户手机号筛选。后端写一个统一的查询接口使用 SQLAlchemy 的过滤链动态拼接条件。前端表格用 Element UI 的 Table 组件配合 Pagination 分页。筛选条件变化时重新请求接口数据量不大时这种传统方案最稳。派单页建议做成“订单详情 保洁员列表”左右布局。左侧展示订单的时间、地址、服务项目右侧展示可派单的保洁员卡片包含今日单量、评分。管理员点击某个保洁员确认后调用派单接口。这里的“可派单保洁员”要过滤掉当天已排满的人避免派单后再被保洁员拒绝。统计页用来展示每日订单数、营收、各服务项目占比。后端可以使用 SQLAlchemy 的func.count、func.sum按日期分组from sqlalchemy import func stats db.session.query( func.date(Order.created_at).label(day), func.count(Order.id), func.sum(Order.total_price) ).filter(Order.status completed).group_by(func.date(Order.created_at)).all()前端用 ECharts 画折线图和柱状图效果非常直观。这块不需要做太复杂能说明问题就行。4. 关键模块拆解鉴权、匹配、通知一个都不能少4.1 基于 JWT 的多角色登录鉴权多角色系统最忌讳的是一套登录接口走天下所有角色都能调用所有接口。我这里用的是 JWTJSON Web Token方案。用户登录成功后后端生成 token并在 token 的 payload 中写入用户 id 和角色信息import jwt token jwt.encode( {user_id: user.id, role: user.role, exp: datetime.utcnow() timedelta(hours12)}, app.config[SECRET_KEY], algorithmHS256 )前端每次请求时在Authorization头里带上 token。Flask 端写一个装饰器解析 token并把当前用户存入g对象def login_required(f): wraps(f) def decorated(*args, **kwargs): token request.headers.get(Authorization) if not token: return jsonify(code401, msg未登录), 401 try: payload jwt.decode(token, app.config[SECRET_KEY], algorithms[HS256]) g.user_id payload[user_id] g.user_role payload[role] except jwt.ExpiredSignatureError: return jsonify(code401, msg登录已过期), 401 return f(*args, **kwargs) return decorated需要角色限制的接口再叠加一个require_role(admin)装饰器。这里要注意前端路由守卫只是体验优化真正的权限控制必须在后端做。因为任何人都可以绕过前端直接调接口前端隐藏按钮并不能保证安全。4.2 订单状态机与防并发处理状态机我在前面已经提过白名单方式。但光有白名单还不够并发情况下两个请求同时修改订单状态可能导致状态被覆盖。比如用户同时点了“取消”和“确认完工”一个 200 一个也 200最终状态取决于谁后提交数据就乱了。处理办法有两个一是乐观锁。在订单表增加version字段更新时带上旧的 versionupdated Order.query.filter( Order.id order_id, Order.version old_version ).update({ status: new_status, version: old_version 1 }) if updated 0: return jsonify(code409, msg订单已被其他操作修改请刷新后重试)二是在关键状态变更时使用数据库行锁SQLAlchemy 里可以用with_for_update()。家政预约系统的并发量不会特别高乐观锁已经完全够用。我更推荐乐观锁因为它不长时间占用数据库连接代码也更好理解。4.3 简单实用的消息通知方案用户下单后保洁员和管理员需要第一时间知道新订单。这里不需要上消息队列或 WebSocket简单方案是在订单状态变更时向相关角色发送站内信或短信。站内信实现成本低建一张notification表包含user_id、content、is_read、created_at。Vue 前端在顶栏轮询未读消息数即可比如每 30 秒调一次/api/notifications/unread_count。短信通知建议接入阿里云或腾讯云短信接口很简单但需要企业资质。个人开发阶段可以用“邮件 站内信”替代或者让管理员手动刷新页面。我实际做的时候给保洁员端加了一个简单的window.setInterval轮询新订单效果足够好。5. 实操中的踩坑记录与优化经验5.1 开发环境配置那些坑Python 和 Node 的环境版本问题是新手最容易卡住的地方。我建议 Python 用 3.8 到 3.11 之间Flask 2.x 都可以。创建虚拟环境是必须的python -m venv venv source venv/bin/activate # Windows 下是 venv\Scripts\activate pip install flask flask-sqlalchemy flask-cors pyjwtVue 这边如果你用的是 Vue CLI先确认 Node 版本不低于 14。安装依赖时如果卡在node-sass建议直接换sass和sass-loader或者用 Element UI 的按需引入减少编译负担。npm install失败时删除node_modules和package-lock.json重新装80% 的问题能解决。前后端联调时最大的坑是跨域。Flask 端安装flask-cors配置允许所有来源from flask_cors import CORS CORS(app, supports_credentialsTrue)Vue 端开发环境下配置代理更优雅在vue.config.js里加devServer: { proxy: { /api: { target: http://127.0.0.1:5000, changeOrigin: true } } }这样前端请求/api/xxx就不会有跨域问题生产环境再用反向代理统一转发。5.2 Flask Vue 联调时最容易出的问题联调阶段我遇到最多的问题是日期格式不一致。Flask 返回的datetime对象默认序列化成2025-05-20T10:30:00这种 ISO 格式而前端组件往往需要2025-05-20 10:30。解决办法是统一用strftime格式化成字符串再返回或者自定义 JSON 序列化器。第二个常见问题是图片上传。Flask 接收文件时要注意配置MAX_CONTENT_LENGTH限制大小。前端用FormData提交文件不要手动设置Content-Type否则文件内容会变成普通字符串。正确写法是const formData new FormData(); formData.append(file, file); formData.append(order_id, orderId); axios.post(/api/upload, formData, { headers: { Content-Type: multipart/form-data } });Flask 端用request.files.get(file)接收保存到uploads目录并把访问 URL 存到订单表。5.3 性能与安全方面的几点建议安全方面最重要的三点密码不要明文存储使用werkzeug.security.generate_password_hash做哈希所有输入参数都要校验尤其是订单价格、用户 id 这类数字参数接口返回不要泄露敏感字段比如用户密码 hash、内部 id 可以转成字符串后返回。性能方面初期订单量不超过 10 万时SQLite 索引完全够用。我建议在order表的user_id、cleaner_id、status、appointment_time上建普通索引查询筛选速度会有质的提升。如果后面并发上来了把数据库切到 MySQL再在 Flask 前面加一层 Nginx 做静态资源服务接口用 Gunicorn 多进程跑整体可以支撑一个中型家政公司使用。6. 项目扩展思路这套架构还能怎么用6.1 通用预约场景的复用家政保洁预约系统的技术架构并不局限于家政。把角色名称和业务字段换一换电梯维修预约、宠物上门喂养预约、上门开锁服务、甚至校园失物招领里的“认领申请”和“信息匹配”都可以用同一套 Flask Vue 前后端分离结构来实现。核心复用点在订单状态机和角色权限模型。任何一个“服务方 需求方 管理方”的 C2B2C 场景都可以拿到这套代码去改。我在另一个项目里做过基于 Flask 的校园失物招领平台里面用相似度算法匹配失物和招领信息原理就是把关键词分词后计算 Jaccard 相似度和家政预约里的“推荐保洁员”逻辑非常相似。所以我的体会是后端一定要把业务逻辑和展示逻辑分离这样换场景时只要改模型层和接口。6.2 从轻量到企业级的演进路线如果这套系统要从本地 demo 走向生产我建议按三条线演进一是支付与发票接入微信支付/支付宝的普通商户接口增加退款流程。二是地图与定位用户下单时选地址调用第三方地图 API 获取经纬度保洁员端用地图导航。三是智能调度根据保洁员位置和订单时间自动派单这一步需要算法介入前期可以先用距离优先的贪心策略。整个演进过程中Flask 端代码可以保持不变Vue 前端逐步组件化最终把每个页面拆成独立模块。如果未来要做小程序Vue 的语法迁移到 uni-app 也比较轻松。所以选 Vue 不只是因为当下方便更是给后续留了一条路。最后分享一个我在实际开发中反复强调的习惯任何状态变更都要记录日志。不要嫌麻烦不要相信“这个操作不会出错”。家政服务行业随时可能遇到用户投诉“保洁员没来但订单显示已完成”如果没有状态日志你是解释不清的。加上一条几十字的日志记录能帮你省掉大量扯皮时间。预约系统的核心不是花哨的功能而是每一步都有据可查、每个角色都只能做自己该做的事。把这两个底线守住了这个项目就算成功了。