☰
宿舍报修系统实战:微信小程序+Python Flask工单管理与状态机设计
2026/10/10 4:03:38 网站建设 项目流程

先说个大家都有共鸣的场景:宿舍楼里的水池堵了、空调不制冷、灯管闪个不停,以前要么跑去找宿管阿姨填纸质单子,要么在群里 @ 维修师傅,报修信息经常石沉大海,修没修、修到哪一步了全靠运气。我自己做过一个“某高校宿舍报修智慧管理系统”,前端用微信小程序,后端纯 Python 写接口,把报修这件事从“填单子打电话”变成“拍照上传、自动派单、进度可见、修完评价”的完整闭环。这篇文章就把整个系统的设计思路、核心代码、踩过的坑一次讲清楚,如果你也在做类似的后勤管理、工单系统,可以直接拿去参考。

1. 整体设计:为什么选“小程序 + Python”这个组合

1.1 先从用户场景倒推需求

做项目最忌讳上来就写代码,先想清楚谁在用、怎么用。宿舍报修系统里主要有三类角色:学生、宿管/维修师傅、后勤管理员。

学生的核心痛点是“报修路径要短”——打开微信就能用,不用专门装 App,拍照传图、选宿舍、填描述、提交,完事。维修师傅的痛点是“活要分得清楚”——谁修的、在哪个宿舍、什么故障、什么时候去的,心里得有本账。管理员的痛点是“数据要看得见”——哪些宿舍报修最多、哪些故障类型频发、师傅响应快不快、修完有没有学生确认,这些都要能统计出来。

所以系统必须有三个端:微信小程序端(学生用)、后台管理端(管理员用,PC 网页)、服务端(Python 写的 API)。小程序端只负责交互和展示,所有业务逻辑都交给后端处理,这样以后要加 Web 端或者 App 端,直接复用同一套 API 就行。

1.2 技术选型的真实理由

后端选 Python 单纯是因为快。Flask 框架写接口非常轻量,一个报修系统的核心接口就那么十来个,用 Flask 比用 Django 少很多“框架感”,路由清晰、调试方便。当然如果你想把权限管理做得更细,Django 自带的 Admin 后台和 ORM 确实省事,但我个人建议中小型项目用 Flask,代码自己掌控,不绕弯子。

小程序端就是原生微信小程序,不引入 uni-app 之类的跨端框架。原因很实在:项目里用到的组件不多,原生的小程序 API 就够用,而且原生开发调试起来最顺手,预览、上传、真机测试都是点几下的事。跨端框架适合“一套代码发多个平台”的场景,这里没有这种需求。

数据库用 MySQL,原因是一个报修系统最终要支撑数据统计,SQL 查起来灵活。用 SQLite 做 demo 没问题,但一旦数据量上来,SQLite 的并发写性能和并发锁问题就会暴露。MySQL 配合 SQLAlchemy 这个 ORM 库,写 Python 代码时不需要自己拼 SQL 字符串,而且迁移表结构也方便。

1.3 系统模块怎么划分

整个系统按业务拆成五个子模块,每个模块的职责边界要清晰,不然写到后面代码会乱成一团:

  • 用户模块:微信登录换取 openid、角色区分(学生/维修工/管理员)、个人信息维护。这里要用到小程序的 wx.login 拿到 code,再传给后端调用微信接口换 openid,注意 openid 是用户在某个小程序里的唯一标识,不能拿它当展示用的用户名。
  • 报修单模块:建单、查询列表、撤销、详情查看。状态机是核心,我这里定义了“待接单-待维修-维修中-待确认-已完成-已撤销”六个状态。
  • 派单模块:管理员手动指派或者按逻辑自动分配,维修工可以主动接单,也可以被派单。
  • 消息通知模块:对接微信订阅消息,站内消息记录也要有一份。学生在报修提交时勾选“希望微信通知我进度更新”,后端在状态变化时触发模板消息推送,让用户不需要一直刷小程序。
  • 统计模块:按宿舍楼、故障类型、时间周期做聚合统计,给管理员看图表。

模块之间通过“状态机 + 事件”驱动,比如学生提交报修,状态变成“待接单”,管理员指派师傅,状态变“待维修”,师傅标记“开始维修”变“维修中”,修完点“完成”变“待确认”,学生确认没问题变“已完成”。这里面最容易被忽略的是“待确认”这个状态——必须让学生确认维修效果,防止师傅自己点完成、实际没修好的情况。

2. 核心功能与数据库设计:一张表说清楚所有状态

2.1 数据库表结构精讲

报修系统核心表就 5 张,设计得不需要特别复杂,但每一张表都有讲究。

用户表(users)最关键的是 role 字段和 openid 字段。openid 做唯一索引,role 用小小的数字表示:1 学生,2 维修工,3 管理员。学生需要绑定学号和宿舍信息,维修工需要绑定维修范围(可以管哪几栋楼),这些关联信息通过 user_profile 详情表或直接冗余在 users 表中都可以。我建议直接加几个字段:name、phone、student_no、dorm_building、dorm_room、work_scope,简单直接,省得关联查询。

报修表(repair_orders)是业务核心,需要重点设计。status 字段用整数存储,比字符串省空间且索引效率高,但要在代码里定义常量并注释清楚。字段大致包括:报修人 id、宿舍楼、宿舍号、联系人电话、故障类型、问题描述、报修图片 URL、分配的维修工 id、管理员 id、状态、优先级、创建时间、开始维修时间、完成时间、学生确认时间。故障类型建议单独建一个字典表或者用固定的枚举值,因为后面统计要按类型分组,不能让学生随便填文本。

维修记录表(repair_actions)做日志留痕,每一条状态变更都记录在案。这个表的作用是让管理员能回溯:这个单子谁接的、什么时候开始修、修了多久、中间有没有拖延。我是用装饰器或者钩子函数,在每次状态变更时自动落一条记录,包括操作人、动作类型、动作描述、操作时间。

通知记录表(notifications)记录所有消息推送。第三方消息推送有频率限制,不能用完就丢,得存库,避免重复推送,也方便排查“为什么用户没收到”。

评价表(repair_feedback)存学生对维修服务的评价,包括服务态度打分、维修质量打分、评价文字、维修结果是否解决。注意这里的评价要锚定到具体的 repair_order_id,一个订单只能有一条有效评价。

2.2 状态机设计与流转逻辑

报修单状态流转是系统的灵魂。我用代码里的常量去定义,而不是直接用字符串:

状态值状态含义触发动作
0待接单学生提交报修
1待维修管理员指派或师傅接单
2维修中师傅点击“开始维修”
3待确认师傅点击“维修完成”
4已完成学生确认“维修完成且合格”
5已撤销学生主动撤销(仅限待接单状态)

这里有几个边界:学生提交报修后,在管理员还没派单前可以自己撤销;已派单未开工时学生不能直接撤,但管理员可以作废;维修结束后必须等学生确认,超过 24 小时未确认可以自动确认并提醒学生,避免订单一直卡着。这个“超时自动确认”是项目上线后遇到真实反馈才加的——有些学生修完就忘了确认,师傅的工单一直挂在那,绩效统计也没法做。

状态变化要记录操作者和时间点。我在 API 层做了一个简单的“可流转状态校验函数”,只有合法的流转才允许被执行,比如从“待维修”不能直接跳到“已完成”,必须经过“维修中”和“待确认”,这样数据就不会被操作错乱。

2.3 消息通知的设计细节

微信订阅消息是这里最容易踩坑的地方。小程序的订阅消息是“一次订阅、一次推送”的模式,而且用户必须主动勾选同意。学生在提交报修时,我弹出授权框让他勾选“允许发送报修进度通知”,就用这一次订阅机会,在状态流转到时把消息推给他。

这里有个经验:不要把推送动作放在状态流转的同一处代码里写死,而是建一个发送队列或者异步任务。原因是微信订阅消息接口有一次性的 access_token 限制,如果多个订单同时流转,要合并 access_token 请求,避免每个订单都去刷新 token。我实际做的时候是写了一个 send_subscribe_message 函数,把 token 缓存到内存里,过期了才重新请求,给消息推送加了简单的“发消息后标记已推送”防止重复推送。

3. 实操过程:小程序的页面搭建与后端接口开发

3.1 小程序端页面与交互

小程序端我只做了四个页面:首页(报修列表)、报修提交页、详情页、个人中心页。重点说提交页和详情页。

提交页交互比较讲究。宿舍楼和宿舍号要滚动选择,且宿舍楼列表从后端拉取,不能写死。故障类型用“标签选择”,比如“水暖”“电路”“家具”“家电”“门窗”“其他”。图片上传用压缩之后再传,不然现在手机拍出来的照片动辄几 MB,直接传原图会很卡。我这里写了一个 compressImage 函数,将图片宽高限制在 1200 像素以内,质量压到 80%,传到后端时大小基本能控制在 300KB 以内。

详情页要展示完整的维修时间线:什么时候报修的、谁接的单、师傅几点到的、修完没有、学生有没有确认。这个时间线从 repair_actions 表里取,每一条记录展示一个节点,前端用纵向线条列表渲染。学生端还可以在待确认状态下填评价。

3.2 后端核心接口的设计与代码实现

我用 Flask 写接口,下面这个函数是报修单创建的核心逻辑,包含了图片处理、初始状态设置和通知占位的处理:

@bp_repair.route('/create', methods=['POST']) def create_order(): data = request.get_json() user_id = g.user_id required_fields = ['building', 'room', 'fault_type', 'description'] for field in required_fields: if not data.get(field): return jsonify(code=400, msg='缺少必填字段: %s' % field) order = RepairOrder( user_id=user_id, building=data['building'], room=data['room'], fault_type=data['fault_type'], description=data['description'], images=json.dumps(data.get('images', [])), status=0, # 待接单 priority=0, created_at=datetime.now() ) db.session.add(order) db.session.commit() return jsonify(code=0, data={'order_id': order.id})

这里要注意的一个坑是微信小程序端 form 表单拿到的数据是 JSON 对象,必须用 request.get_json() 解析,不能用 request.form。之前就有人踩过这个坑,小程序端明明传了数据,后端就是取不到。

派单接口是管理端最核心的操作。管理员看到一个待接单的报修,可以手动指定维修工,也可以一键“按区域自动指派”。自动指派逻辑是:先找负责这栋楼的维修工,如果有多个,按照“当前待维修单数量 + 最近完成单数”做一个负载均衡指标,选最少的那一个。这个逻辑在数据量不大的时候完全够用:

def auto_assign(order): building = order.building workers = Worker.query.filter( Worker.work_scope.contains(building), Worker.available==True ).all() if not workers: return None def workload(w): pending = RepairOrder.query.filter_by( assignee_id=w.id, status.in_([1, 2]) ).count() return pending best = min(workers, key=workload) order.assignee_id = best.id order.status = 1 db.session.commit() return best

3.3 管理后台的统计报表实现

管理员端我单独做了一套 PC 网页,主要看几个东西:当前未完成工单、各宿舍楼报修排行、故障类型分布、维修工绩效。这些统计全部通过 SQL 聚合来做,例如故障类型分布:

@bp_admin.route('/stats/fault_type') def stat_fault_type(): rows = db.session.query( RepairOrder.fault_type, func.count(RepairOrder.id) ).group_by(RepairOrder.fault_type).all() return jsonify(code=0, data=[{'fault_type': k, 'cnt': v} for k, v in rows])

报表展示我用的 ECharts 的柱状图和饼图,数据从后端接口拉取后前端拼 option。这个模块虽然后端逻辑简单,但有一个设计细节很重要:统计数据要能按时间范围筛选,否则所有历史数据混在一起,没法看月度变化趋势。我加了一个简单的 date_from 和 date_to 参数,前端日期选择器选好之后传到后端。

4. 常见问题与排查技巧:自己踩过的坑都在这里

4.1 图片上传失败的 3 个常见原因

小程序端图片上传最常遇到的就是“后端能传文字,图片死活传不上”,排查下来无非三件事:第一,小程序 wx.uploadFile 的 name 字段值必须和后端 request.files.get('file') 里的 key 对上,这个看起来简单但经常被忽略;第二,后端 Python 的 request.files 在 Flask 里是临时文件,要先保存再处理,不要直接拿过来就读取,否则文件句柄关闭后读不到内容;第三,图片太大导致超时,需要前端压缩,后端也要设置 MAX_CONTENT_LENGTH 防止恶意上传大文件。

还有一个隐蔽的问题是微信开发者工具的“不校验合法域名”开关。本地调试时通常要勾选跳过域名校验,但如果发布上线后忘了在微信公众平台配置服务器域名,真机上就会报“url not in domain list”,所有请求直接失败。这个事我栽过一次,后来专门写了一个检查清单,上线前逐项核对。

4.2 微信登录 session 失效问题

小程序端每次打开都会调用 wx.login 获取 code,然后后端拿 code 去微信接口换 openid 和 session_key。这里有个性能问题:如果每次都请求微信接口,一方面慢,另一方面会被限频。我的做法是登录接口做两层判断,先用 code 换 openid,然后查数据库有没有这个用户,有就直接返回业务 token,没有才走完整注册流程。业务 token 我用的就是简单的 UUID 存 Redis,设置 7 天过期,配合小程序端的 wx.checkSession 判断登录态是否过期。

这个 token 比直接用 session_key 安全得多,因为 session_key 是微信的敏感信息,不应该下发给前端。如果小程序端需要解密手机号之类的功能,也是要在后端用 session_key 处理,前端不要碰。

4.3 状态流转“卡死”问题排查

有段时间报修订单一直卡在“维修中”,师傅说早修完了,但学生端没看到确认按钮。排查后发现是师傅端的“完成维修”按钮调用的状态更新接口报错了,错误原因是在状态流转时,系统默认要发送订阅消息,而订阅消息模板 ID 配置错了,导致整个事务回滚。这个“通知失败连累业务失败”的设计问题让我反思了一下,后来把所有状态流转和消息推送拆开了——业务先成功,消息失败只记个日志,不影响主流程。这是做这类系统很重要的一个原则:辅助功能永远不能卡主业务。

4.4 常见问题速查表

问题排查方向
小程序请求不通域名备案、开发者工具校验关闭、HTTPS 证书
图片传不上去name 字段匹配、文件保存路径、大小限制
登录一直失败code 过期、openid 未入库、token 过期
订阅消息收不到用户没点授权、模板 ID 错误、access_token 过期
状态不流转检查可流转校验函数、看是否有报错日志
报表数据不对SQL 聚合条件、时间筛选参数、时区问题

5. 可以继续深挖的扩展方向

报修系统做到这里基本能用,但如果想往“智慧管理”再走一步,有几个方向值得思考。

第一个方向是引入故障预测和巡检。现在宿舍报修是被动响应式,坏了才报。可以结合历史报修数据,统计出哪些宿舍楼、哪些部位的故障概率高,比如某栋楼的洗手间管道平均 3 个月堵一次,就可以主动安排巡检保养。这个并不需要多高深的算法,用简单的统计学分析就能给管理员提供决策参考。

第二个方向是维修工绩效与评价体系。现在只是记录工单完成量,可以做得更细:接单响应时长、维修耗时、学生评价分、返修率。返修率是一个很关键的指标——如果一个师傅修完一周内同一个宿舍又报修同样的问题,说明第一次维修质量可能有问题。

第三个方向是消息通知多样化。目前只有微信订阅消息,可以扩展企业微信通知给维修工和管理员,让他们不用打开小程序也能收到新工单提醒。维修工端里还可以加语音播报“新的报修单来了”。

第四个方向是数据大屏。管理员看报表还是格子视图,如果能做一个大屏,把实时维修单数、今日完成量、平均响应时间、故障类型 TOP5 用大字号图表展示在后勤服务中心的大屏上,视觉冲击力和管理效率都会提升不少。

说到最后,我个人在这个项目里最深的体会是:做一个系统,代码和架构只占一半,另一半是对真实业务场景的理解。宿舍报修这件事看着简单,但“谁负责、怎么派、怎么量绩效、学生体验如何”全都要想清楚。你写的每一个状态流转、每一个通知触发,背后都是一个现实中会发生的动作。如果你也在做类似的项目,建议先花一个下午去跟宿管阿姨聊聊天,看看她们现在是怎么记事的,把流程理顺了再写代码,这样系统做出来才不会只是一个“看起来很美”的摆设。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询