ARTICLE DETAIL

建站实战干货

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

高校宿舍报修系统实战:从多角色流程到uni-app跨端开发

2026/9/19 4:28:27 拓冰建站 浏览量
高校宿舍报修系统实战:从多角色流程到uni-app跨端开发 前阵子帮一所高校后勤处做宿舍报修系统学生报修渠道还是靠前台填纸质单子宿管转给维修师傅师傅修完再到前台登记整个过程不光慢而且修没修、怎么修的很难查。所以就有了这个项目一个高校学生公寓宿舍报修管理系统覆盖学生端、宿管端、维修工端和管理员端小程序端面向学生App端面向高频使用的维修人员一套代码同时编译上线。这篇文章就把我实际设计、开发这套系统的思路和踩坑记录整理出来适合正打算做类似课程设计、毕业设计或者想在企业实习里接一个小型全栈项目的同学参考。1. 项目定位与业务模型拆解1.1 多角色从哪里来宿舍报修的真实场景高校宿舍报修看起来只是“坏东西报告一下”的小事但一旦进入管理流程参与者就不止一个。第一个是学生宿舍里的灯管坏了、空调不制冷、水龙头漏水学生要能快速提交报修并且知道维修进度。第二个是宿管宿管阿姨需要先核实报修信息判断是不是真的需要上门部分情况可能是学生不会操作比如空调遥控器没电根本不需要维修师傅跑一趟。第三个是维修工维修师傅按区域接收工单接单后上门处理处理好之后回填维修内容和用料。第四个是系统管理员辅导员或者后勤领导需要看到整体维修数据哪些宿舍楼报修率高、哪些故障类型反复出现、每个维修工的响应速度和工作量。这个多角色模型是整个系统的核心。如果只做一个“学生上报、维修工处理”的简单流程宿管和管理员就没有存在感实际在高校里也很难落地。真正的宿舍报修不是纯 C 端下单它是一个需要逐级审核、分工协作的流程。所以我在设计角色的第一步是跟后勤老师把流程会议开清楚而不是直接画页面。1.2 为什么同时做App和小程序而不是只做一个最初甲方说“想要一个App”我第一反应是“学生装 App 成本太高了”。高校学生手机内存本来就紧张让所有人为了报修专门下载一个App体验很差。小程序的打开路径太短了微信里搜一下就能进学生基本零学习成本。所以学生端优先选择了微信小程序。但维修工端不一样。维修师傅可能在楼道里、地下室网络不稳定需要经常拍照、大图上传还要实时接收新工单的通知。小程序的订阅消息限制多后台驻留能力弱而 App 通过推送服务可以更可靠地通知到维修师傅。这时候一个安卓/iOS的App就很有必要。问题来了做两个独立前端项目开发和维护成本都翻倍。所以我在技术选型时直接用 uni-app一套 Vue 风格的代码编译成微信小程序和手机 App。这样“小程序 App”不是两个团队各自维护而是同一份代码按需发行。后面我也会专门讲 HBuilderX 发行微信小程序的具体操作。1.3 核心业务流程与状态机报修工单不能只有“已提交/已完成”两个状态否则一旦中途出现扯皮责任无法追溯。我把工单生命周期拆成了几个明确状态待审核、待接单、维修中、待验收、已完成、已取消。学生提交报修后进入“待审核”宿管审核通过变成“待接单”维修工接单变成“维修中”维修工回填处理结果后进入“待验收”学生确认没问题才“已完成”。中途如果学生撤销或者宿管认为不需要维修就进入“已取消”。这个状态机看起来简单实际实现时很容易出错。比如“待接单”状态下两个维修工同时点了接单数据库如果不做限制工单就可能被两个人同时领走。后面我会讲怎么用条件更新加事务解决。还有一个容易忽略的点每一次状态变化都要记录日志谁在什么时间把工单从哪个状态改成了哪个状态。这样一旦有纠纷打开日志就能看明白。2. 技术选型与工程初始化2.1 技术栈选择的取舍前端我用 uni-app核心原因是跨端复用。后端我用的是 Spring Boot 加 MyBatis-Plus配合 MySQL。为什么不把后端也搞成 Node.js因为做这个项目的学生大多在学校里学过 JavaSpring Boot 的生态和资料最全遇到问题更容易查。如果团队更熟悉 Python也可以用 FastAPI但权限控制和事务处理上需要自己多写一些代码。认证和权限我选了 Sa-Token而不是 Spring Security。原因是这个项目角色明确接口数量也不多Sa-Token 上手简单十几分钟就能跑通登录、鉴权、权限码校验。Spring Security 功能更强大但配置复杂度对一个小项目来说有点吃力。如果以后要做企业级大型系统再迁移到 Spring Security 也来得及因为权限模型本身是通用的。数据库表我建议至少设计用户表、角色表、用户角色关联表、报修工单表、工单状态日志表、通知消息表。如果要做评价功能再加一张评价表。这些表的关系和字段设计我在第 3 部分会给出建表 SQL。2.2 用 HBuilderX 创建项目并发布到微信小程序工具下载不再多说安装 HBuilderX 之后新建项目时选择“uni-app”模板框架选 Vue 3。创建出来的目录里有pages、static等标准结构。我在manifest.json里配置小程序的 AppID这个 AppID 需要在微信公众平台注册小程序账号后获得。如果是个人开发者也可以使用测试号但测试号不能进行真机预览的完整流程最好还是注册一个正式的小程序账号。运行到微信小程序之前需要先在 HBuilderX 菜单里点击“运行”→“运行到小程序模拟器”→“微信开发者工具”。前提是电脑上已经安装了微信开发者工具。第一次连接时HBuilderX 会在本机启动一个服务微信开发者工具需要打开服务端口否则无法预览。这个端口我遇到过不少次被占用的情况处理方法是在微信开发者工具的安全设置里换一个空闲端口再重新运行。真正要上线需要点击“发行”→“小程序-微信”uni-app 会生成一个dist/build/mp-weixin目录然后在微信开发者工具里导入这个目录上传版本再去微信公众平台提交审核。这里有个很多新手容易踩的坑直接把整个 uni-app 源码工程导入微信开发者工具那肯定跑不起来必须导入编译后的mp-weixin目录。2.3 小程序备案信息怎么填现在新上线的小程序都需要做 ICP 备案否则无法通过审核。备案时填“服务内容”和“备注信息”是有讲究的。如果是学校作为主体备注要写清楚本小程序用于高校学生公寓宿舍报修管理方便学生提交报修工单、查询维修进度维修人员接收和反馈工单不涉及经营性服务不收集与报修无关的隐私信息。类目建议选择“教育”下的“教育信息服务”或者“生活服务”下的“生活缴费/维修服务”具体要看微信审核类目是否允许。如果是个人开发者做课设演示主体填自己名字备注尽量突出“个人学习演示”“非经营性”但个人主体的小程序类目会受限可能无法使用维修服务相关类目。这种情况下直接用小游戏类目或者工具类目做演示没问题但不能用于真实校园运营。我的做法是先把备案信息按正式场景写好提交审核前再根据驳回意见微调不要干等所有材料齐全才开始。3. 数据库设计与接口设计3.1 用户、角色、工单、日志几张核心表我先给出最小可用版本的建表 SQL。sys_user存用户基本信息sys_role存角色sys_user_role存用户和角色的关联。为什么要拆一张关联表因为一个用户可能同时是宿管和维修工尤其在人员紧张的小校园里一个人身兼两职很常见。如果只在用户表里加一个 role 字段碰到这种场景就没办法表示。CREATE TABLE sys_user ( id bigint(20) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL, password varchar(100) NOT NULL, real_name varchar(50) DEFAULT NULL, phone varchar(20) DEFAULT NULL, avatar varchar(255) DEFAULT NULL, school_id bigint(20) DEFAULT NULL, status tinyint(4) NOT NULL DEFAULT 1, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) );sys_role很简单就是 id、角色编码、角色名称。sys_user_role就两个字段user_id 和 role_id。实际使用的时候我发现不需要额外再建一张“宿舍楼”表直接在用户表里放school_id或者dormitory_id就够了需求复杂再拆。repair_order是核心业务表字段要覆盖整个状态流转。我把整个建表放到下一节因为字段需要结合状态机一起解释否则很抽象。3.2 报修工单字段与状态流转设计repair_order表我最终设计了这些字段id主键order_no工单编号用于给学生和宿管看格式类似BX20250601001user_id提交报修的学生用户IDdormitory_id宿舍楼/宿舍房间标识前端用字符串拼出“12号楼315”category故障类型比如灯/水/空调/门窗/其他description文字描述最长 500 字image_urls图片地址多个用逗号分隔或者用 JSON 数组存status当前状态用数字枚举0待审核 1待接单 2维修中 3待验收 4已完成 5已取消acceptor_id接单维修工的ID初始为空accept_time接单时间finish_time维修完成时间handle_result维修结果描述cancel_reason取消原因create_time提交时间update_time更新时间状态流转我写在代码里而不是数据库触发器里方便后期加逻辑。比如学生提交报修后状态为 0宿管审核通过改为 1维修工接单时先执行带条件的 UPDATE条件包括status 1 AND (acceptor_id IS NULL OR acceptor_id #{userId})这样能防止两个人同时抢单。这里有一个关键点UPDATE 语句影响行数如果是 0说明工单已经被别人接走了要立即回滚并提示“手慢了”。3.3 RESTful接口清单与示例接口设计遵循 RESTful 风格但不要过度设计。我按角色拆接口学生端和宿管端大多数是 GET 查询维修工端是 POST 提交。需要重点实现的接口POST /api/order/create学生提交报修参数带 category、description、imageUrlsGET /api/order/my-list查询当前用户提交的工单列表分页GET /api/order/detail/{id}工单详情包含日志PUT /api/order/audit宿管审核参数 id 和 auditResultPUT /api/order/accept维修工接单PUT /api/order/start维修工开始维修状态从待接单变为维修中PUT /api/order/complete维修工完成并上传维修结果PUT /api/order/confirm学生确认完成PUT /api/order/cancel取消工单拿接单接口举例控制层代码大概是这样PutMapping(/accept) RequireRole(repairer) public Result accept(RequestBody AcceptOrderReq req) { int rows repairOrderService.acceptOrder(req.getOrderId(), LoginUtil.getUserId()); if (rows 0) { return Result.error(工单已被接走或状态已变化); } return Result.ok(); }repairOrderService.acceptOrder内部直接执行条件更新而不是先查询再更新。这就是为了避免并发问题。很多同学习惯“先 select 再 update”这在小流量下没问题但在抢单场景里很容易出脏数据。4. 多角色权限控制的落地实现4.1 基于RBAC的权限模型设计思路前面提到用户角色用关联表这样能支持一人多角色。但实际开发中有时候一个用户虽然挂了多个角色他登录的默认身份只能选一个。我在用户表里加了一个current_role字段每次登录后用户可以切换当前身份但切换行为要记录日志。这个设计在高校里很实用宿管阿姨同时也能维修灯泡她上班时用宿管身份审核工单下午换上维修工身份去处理维修任务。RBAC基于角色的访问控制的核心是“用户-角色-权限”三层。在这个项目里我不需要做到细粒度的按钮权限只控制接口即可。比如只有repairer角色能调用接单接口只有student角色能提交工单只有admin角色能看到统计报表。后端用 Sa-Token 校验角色编码前端再根据角色做菜单隐藏双保险比单端控制安全得多。4.2 后端注解加拦截器实现接口鉴权我用 Sa-Token 的拦截器配合自定义注解RequireRole做权限控制。每个接口上标注需要的角色拦截器在进入 controller 前把不带 token 的请求挡掉再检查登录用户是否包含某个角色。Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface RequireRole { String[] value(); }拦截器内部逻辑很简单从请求头拿到 token调用 Sa-Token 的StpUtil.getLoginId()拿到当前用户ID然后查它的角色集合判断是否包含注解里的任一角色。这个逻辑看上去不复杂但有几个细节容易被忽略。第一token 过期时间不要设得太短否则维修工干完一个活再提交结果时发现 token 过期气得骂人。我建议移动端 token 有效期设 7 天。第二角色集合每次请求都查一次数据库很浪费可以在登录时把角色编码列表放到缓存里之后每次从缓存取。第三状态被禁用的用户即使 token 没到期也要在拦截器里检查 status否则可以一直操作。4.3 前端根据角色动态显示菜单与按钮前端这边我在全局状态里保存用户角色。每个页面登录后先调用/api/user/info拿到角色列表然后按角色渲染底部导航。学生端看到“我要报修”“我的工单”宿管端看到“工单审核”“统计概览”维修工端看到“待接单”“我的任务”。比如在一个报名单页面上学生只能看到“提交报修”按钮宿管不能直接提交维修工看不到学生提交页面。这个思路不复杂但 uni-app 里 tabBar 是静态配置的无法根据角色动态切换底部导航我会改成在 index 页面里根据角色switch到不同的子页面或者用自定义导航栏替代系统 tabBar。简单做法是每个角色单独写一个首页用户登录后跳转到对应的首页这样代码结构更清晰也避免了在同一个页面里写一大堆v-if。5. 关键业务模块的实现细节5.1 学生提交报修表单校验与图片上传学生端最核心的页面就是提交报修。第一步选故障类型第二步填写描述第三步拍照上传。这里最大的坑是图片上传。如果直接把 base64 文件丢给后端接口可能瞬间超时。我采用uni.uploadFile上传到后端/api/file/upload后端获取 MultipartFile 后再上传到服务器本地磁盘或者对象存储。上传完成后后端返回图片 URL前端再把 URL 放在工单请求里提交。图片压缩是一个容易被忽略的点。现在的手机拍照一张图 5MB 以上宿舍报修通常要传三四张图不压缩直接传到服务器既浪费流量又拖慢速度。我建议前端用uni.compressImage把图片压缩到 800px 宽度质量参数设 0.7这样一张图基本能控制在 200KB 以内。后端也加一个校验图片后缀必须符合 jpg/png/webp大小不能超过 5MB。不要只信任前端的文件类型伪造上传的文件扩展名非常简单。表单校验方面我不用第三方库直接在提交时写简单的if判断故障类型必选描述至少 5 个字符图片数量不能超过 6 张。描述内容不能用纯空格去掉首尾空格后为空也不能提交。5.2 维修工接单、更新进度与超时处理维修工端的接单列表很重要要支持按宿舍楼筛选。接单操作我们前面讲过用条件更新防止抢单。维修工开始维修后状态变为“维修中”这里也要更新start_time。完成维修后维修工要填写handle_result比如“更换灯管1支费用0元”然后上传一张维修完成后的照片状态变为“待验收”。超时处理是甲方后来加的。要求就是维修工接单后如果 2 小时不开始维修系统要自动提醒。我在定时任务里每分钟扫一次工单把正在维修中且超过 2 小时没有更新进度的工单标记为“超时”同时发送消息给维修工和管理员。这个定时任务用 Spring 自带的Scheduled就行不需要引入复杂框架。但要注意测试环境里不要真正等 2 小时我把超时时间抽成配置项测试时改短一些。5.3 消息通知如何通知相关人消息通知是用户感知系统好坏的重要环节。学生提交报修后维修工要赶紧知道维修工处理完学生要马上收到结果。最简单可靠的方式是前端轮询接口每 30 秒拉一次未读消息数。这个方案实现简单服务端只需要维护一张message表每条消息有user_id、content、is_read字段。但轮询消耗请求量如果评估全校只有几百个活跃用户问题不大。如果想更高级一点可以接入微信订阅消息。学生提交报修时请求授权发送“工单状态通知”维修工完成时后端调用微信接口发送订阅消息。这个方案的难点是小程序需要用户在提交工单时预先订阅一次而且每次订阅只能发送一条消息。我的经验是正式项目里可以采用“订阅消息 轮询兜底”的组合订阅消息能及时推送最好推送失败也不影响用户下次登录时看到红点。6. 前后端联调与常见问题排查6.1 小程序真机预览连不上本地后端开发中最常见的问题是真机预览时接口请求失败。模拟器上能用localhost访问后端但到了真机上手机访问localhost是它自己当然连不上。解决方法是把后端接口地址改成开发电脑在局域网里的 IP同时在 Spring Boot 的application.yml里把server.address设为0.0.0.0保证局域网内的设备能访问。如果使用了微信开发者工具预览还需要在本地设置里勾选“不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书”。这个选项只适用于开发调试真机预览时需要确保手机和电脑在同一个 WiFi 下。如果校园网做了 AP 隔离手机和电脑虽然都在同一个 WiFi 下但互相不能访问那就只能改用云服务器调试或者用手机热点让电脑和手机在一个网段。6.2 微信小程序备案被驳回的常见原因小程序备案的驳回原因五花八门但我整理下来主要集中在这几类。第一主体信息不一致比如用公司名义申请但备注里写的是个人项目审核人员一看就知道有问题。第二服务内容描述太宽泛比如只写“维修服务”没有体现具体场景会被要求补充用户群体和功能范围。第三隐私政策缺失或没有在小程序内展示报修系统会涉及用户的宿舍房间号、电话这些属于个人信息必须有明确的隐私保护说明。我的填写模板是本项目用于高校学生公寓宿舍报修管理学生用户提交公寓内设施故障报修申请宿管人员和维修人员根据工单信息进行审核与维修反馈系统处理学生提交的宿舍号、联系方式、报修图片等信息仅用于维修对接不用于其他用途。这样主体用途、数据范围、角色关系都交代清楚了。6.3 两个维修工同时抢单的脏数据排查我在联调阶段抓到一个典型问题两个维修工同时刷新待接单列表看到同一个工单都点了接单。第一次用“先查询再更新”的写法结果两个人都显示接单成功工单的acceptor_id却只有一个。原因是两个请求同时读到状态等于 1然后先后执行更新后更新的覆盖了先更新的。后来我改成UPDATE repair_order SET status 2, acceptor_id #{userId} WHERE id #{orderId} AND status 1 AND acceptor_id IS NULL再通过返回行数判断是否成功。这个问题排查起来并不难但值得重视因为它说明即使一个简单的小项目也要有最基本的并发意识。6.4 常见问题速查表这里把我在整个项目里遇到的高频问题整理成一个速查表方便后面做同类项目时快速定位。现象可能原因解决方案小程序模拟器无法加载接口没勾选不校验合法域名开发工具详情设置里勾选“不校验合法域名”真机上传图片失败图片太大或后端路径没配静态资源映射前端压缩图片后端配置/file/**映射到上传目录工单被两个人同时领取先查询再更新没有加状态条件改成UPDATE ... WHERE status 待接单角色菜单不切换登录后没有重新获取用户信息确保登录后调用/api/user/info并使用角色数据刷新状态小程序备案被驳回服务内容描述太宽泛按“面向高校学生的宿舍报修场景”重新填写App端扫码功能失效没有配置少量硬件相关的权限检查 manifest 里的相机权限以及相关模块是否勾选最后再分享一个小技巧。如果你也是边学边做这个项目不要一上来就把后端所有接口写完再连前端。我的习惯是先把“学生提交工单、维修工接单、完成工单”这条主线跑通再加角色权限、通知、统计报表。主线通了你对整个系统的信心就有了后面的角色权限、状态日志这些都只是往框架里填功能而已。