ARTICLE DETAIL

建站实战干货

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

SpringBoot与微信小程序开发校园报修系统:从业务梳理到工单状态机落地

2026/10/1 12:05:28 拓冰建站 浏览量
SpringBoot与微信小程序开发校园报修系统:从业务梳理到工单状态机落地 前段时间帮学校后勤处做了一个桃李园速修系统选题其实挺朴素的校园里空调坏了、灯管不亮了、水龙头漏水以前靠口头报修、微信群喊话经常出现报修无门、进度全凭运气的情况。这个系统的目标就是让校园设备设施报修走向数字化——学生和教职工通过微信小程序提交报修维修工接单干活管理员统筹派单和监管进度。技术栈用的是后端SpringBoot、前端微信小程序前后端分离。如果你正在做类似的校园服务类毕业设计或者第一次接触SpringBoot加微信小程序这套组合这篇文章可以帮你少走很多弯路。我做这个系统前后花了大约一个半月走了不少弯路也踩了不少坑。接下来不按官方文档那种顺序讲而是按我实际开发时思考问题的路径把关键设计、核心代码、踩坑点一次性说透。1. 桃李园速修系统的业务底色从报修找不到人说起做系统之前我花了两天时间把后勤报修的现状摸了一遍。之所以先谈业务是因为这类管理系统的难点从来不在代码而在流程是否想清楚。报修这件事表面上就是一个用户提交、工人维修的简单动作但真正跑起来你会发现角色至少有三个报修人、维修工、管理员每个人的诉求不一样。1.1 三个核心角色和各自的工作台报修人普通学生/教职工要的是提交方便、能看到进度、修完有个说法。维修工要的是任务清晰、地点明确、干完能标记结果。管理员要的是知道谁报修了、单子派给谁了、有没有超时耽误、哪类问题频繁出现。所以我最终把系统拆成了三个视图登录后按角色动态渲染。报修人看到的是我要报修和我的报修记录维修工看到的是待接单池和我负责的工单管理员看到的是全量工单列表和派单操作入口。这个设计看起来简单却决定了后面整个接口划分和权限控制的走向。1.2 工单状态机整体框架里最值得先画的一张图我没有先写代码而是先把一条报修的完整生命周期列了出来。最终敲定的状态流转是这样一条主线待派单用户提交报修管理员尚未处理已派单管理员指定维修工等待维修工接单维修中维修工接单开始上门维修待确认维修工上传完工说明等待报修人确认已完成报修人确认无误整个工单关闭已取消报修人在任意未完工阶段主动取消或管理员判定无效后关闭除此之外还有两个分支超时未接单可以撤回重派完工后报修人对维修质量不满意可以追评或申请重新处理。状态机定下来之后写后端接口就变成了填空题每个操作对应一个状态迁移每个迁移只允许特定角色触发。比如派单只有管理员能做接单只有维修工能做确认完成只有报修人能做。这套状态机的定义是整个项目的地基后面所有功能都围绕它展开。强烈建议你动手前先把这条线画出来宁可多花一天也不要边写边想。2. 为什么选SpringBoot加微信小程序这对组合项目选题一开始其实有过摇摆。同组的同学有的用纯Vue网页端有的用UniApp打包成多端我还见过有人直接上Android原生。最后我坚持选了SpringBoot加微信小程序理由如下。2.1 后端选SpringBoot快速落地和生态成熟是核心诉求SpringBoot在这个场景下几乎是标准答案。第一它内置Tomcat一个jar包就能跑部署成本低。第二Spring生态里MyBatis-Plus、Spring Security这些组件都很成熟CRUD开发效率极高。第三校园里做毕设或者中小型项目Java技术栈的资料最多遇到问题一搜一大把。对于这种校园内部系统数据量级不大不需要上微服务单体应用加合理分包就够了。我采用的是经典分层controller对外提供接口service处理业务逻辑mapper操作数据库。没有引入太复杂的东西后续维护也轻松。2.2 前端选微信小程序零安装触达用户登录成本低报修场景里报修人是全校师生你不可能要求他们下载一个App也不方便让他们打开浏览器输入网址。微信小程序完美解决了这个问题扫码即用、不占桌面、用完就走。更关键的是微信提供了一套用户身份识别链路wx.login()获取code后端拿code换openid直接作为用户唯一标识。相比网页端需要手机号验证码注册登录这个小程序天然就解决了我是谁的问题。我也对比过UniApp。如果你本身熟悉VueUniApp确实能一套代码跑多端但代价是要处理各平台差异而且对于只需要微信小程序一个端的场景等于白白引入一层抽象。原生微信小程序虽然语法有点非主流但针对性强直接使用微信官方能力出问题也好排查。2.3 版本选型建议SpringBoot 2.7还是3.x这里是我整个项目遇到的第一个大坑后面会专门讲。先说结论如果你的开发机装的是JDK 8老老实实选SpringBoot 2.7.x如果你已经切到JDK 17可以上SpringBoot 3.x。SpringBoot 3.x把javax包换成了jakarta包很多老教程、老依赖会直接编译不过。做毕设和中小项目稳定压倒一切我最终用的是SpringBoot 2.7.18配合JDK 8跑起来非常安静。3. 后端设计与实现把工单的状态流转做扎实后端部分是整个系统的核心我按表结构、认证、业务接口、统一处理四个层次来讲。3.1 数据库表结构五张核心表别过度设计我用MySQL建了五张表用户表、报修工单表、评价表、通知表、以及一个简单的系统配置表。用户表的字段不多重点关注三个openid、role、phone。openid是用户唯一标识role区分普通用户、维修工、管理员phone是方便维修工上门前联系。我这里没有做复杂的权限框架就是靠role字段加拦截器判断。报修工单表是最主要的表字段包括order_no工单号用时间戳加随机数生成、reporter_id报修人ID、device_name设备名称、location位置、description故障描述、images图片URL多个用逗号分隔、status状态码、assignee_id指派的维修工ID、finish_time完工时间。评价表比较简单记录工单评分和文字评价挂在工单ID下面。通知表记录消息比如派单通知、完工通知为后续接入订阅消息留好位置。这里有一个我踩过的坑图片字段一开始想建单独的表后来发现一张报修单最多三张图逗号分隔存的查询成本最低没必要一张表。这种小决策其实处处体现场景决定设计。3.2 登录身份认证code换openid再用JWT管会话小程序端调用wx.login()拿到一个临时code这个code只能用一次有效期五分钟。后端拿到code后去微信的jscode2session接口换openid和session_key。这里要注意session_key不需要存数据库它是微信小程序加密数据的密钥我们这个系统暂时用不到。拿到openid后我给出了一套JWT令牌把userId和role写进token设置七天后过期。小程序端每次请求在header里带上Authorization字段后端拦截器解析token拿到当前用户和角色。这套方案的好处是服务端无状态小程序端只要有token就能玩得转不需要额外维护session表。上核心代码。小程序的登录页逻辑像这样wx.login({ success: async (res) { if (res.code) { const loginRes await request.post(/auth/login, { code: res.code }); const { token, userInfo } loginRes.data; wx.setStorageSync(token, token); wx.setStorageSync(userInfo, userInfo); // 根据role跳转到不同首页 wx.switchTab({ url: userInfo.role 2 ? /pages/admin/index : /pages/index/index }); } } });对应的SpringBoot接口长这样RestController RequestMapping(/api/auth) public class AuthController { Autowired private AuthService authService; PostMapping(/login) public Result login(RequestBody LoginRequest request) { return Result.success(authService.login(request.getCode())); } }loginService里做的事情三步调微信接口换openid、查用户表判断是否已存在、生成JWT返回。如果用户不存在就自动注册角色默认普通用户。这个查不到就自动注册的逻辑简化了用户管理校园内部系统完全够用。3.3 工单接口一个操作对应一次状态迁移工单相关的接口我拆成了两类查询类接口按角色返回不同的数据范围。普通用户只看自己的工单维修工看分配给自己或待接单的工单管理员看全部工单。这种查询隔离全靠请求里带上当前登录用户信息后端从中截取userId和role不能信任前端传参。状态流转类接口对应我前面说的状态机。每个接口内部第一步都是校验当前状态是否允许执行这个操作// 维修工接单示例 PutMapping(/{id}/accept) public Result accept(PathVariable Long id) { // 1. 判断工单存在 // 2. 判断当前用户是维修工 // 3. 判断工单状态必须是已派单且assigneeId等于当前用户 // 4. 更新状态为维修中 // 5. 发送通知给报修人 return Result.success(); }别看逻辑简单这个校验顺序非常重要。我当时漏了第三步结果测试的时候发现任何一个维修工都能接别人的单子后来补上才正常。状态流转千万别写成想改就改每个迁移都问三个问题当前状态下这个操作合法吗操作用户有权限吗有没有冲突操作正在发生3.4 统一响应和全局异常处理提升联调效率的事我给所有接口返回包了一层Result对象结构是code、message、data三个字段。前端请求封装只看code200代表成功其他按提示弹窗展示。这样做的直接好处是联调的时候前端不用解析各种乱七八糟的返回结构后端处理异常也不需要到处写try-catch。全局异常处理我用了Spring的RestControllerAdvice把参数校验异常、业务异常、未知异常分别处理。特别是业务异常统一抛BizException带上自定义错误码和中文提示。比如工单状态不允许该操作这种前端直接弹提示后端代码也清爽了很多。4. 小程序端登录态、请求封装与列表加载小程序端我按登录、请求封装、列表分页、角色适配四个模块做的。这里重点讲几个通用性强的细节。4.1 wx.login() 的正确打开方式wx.login()是微信小程序获取登录凭证的核心API但它只负责拿code不负责给前端任何用户信息。正确流程是我上面写的小程序端拿code后端完成code换openid再把token交还给前端存储。很多人刚接触会以为wx.getUserInfo能直接拿到用户身份其实那个只能拿昵称头像不能作为唯一标识。还有一点容易踩坑wx.login()在真机和模拟器上的表现不完全一致。模拟器上code获取正常但真机上偶尔会因为网络授权弹窗导致code获取失败。稳妥做法是在App启动或首次进入页面时先调用wx.login()拿到code后做防重复处理避免用户操作到一半token过期还要重新登录。4.2 请求封装把wx.request包装成Promise原生wx.request走的是回调风格嵌套两层就开始混乱。我封装了一个request工具统一处理baseURL、token注入、错误提示和加载态。核心逻辑是这样const request (method, url, data) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL url, method, data, header: { Content-Type: application/json, Authorization: wx.getStorageSync(token) || }, success: (res) { if (res.data.code 200) { resolve(res.data); } else { wx.showToast({ title: res.data.message, icon: none }); reject(res.data); } }, fail: (err) { wx.showToast({ title: 网络异常请检查连接, icon: none }); reject(err); } }); }); }; module.exports { get: (url, data) request(GET, url, data), post: (url, data) request(POST, url, data), put: (url, data) request(PUT, url, data) };注意上面做了统一错误处理这省去了每页重复写toast的麻烦。副作用是后端所有接口必须严格遵守Result格式否则错误提示会乱。这也是为什么我前面坚持统一返回结构的原因前后端事先约定好协议联调效率高很多。4.3 工单列表的分页与加载更多报修工单的列表页是整个前端最复杂的模块因为不同角色要展示的内容不同而且数据量会随运行时间迅速膨胀。我做了一个通用的分页列表模式使用PageNum加PageSize每次请求加载一页触底时自动加载下一页。小程序里实现触底加载非常简单页面配置里开启onReachBottom事件然后调用加载函数onReachBottom() { if (this.data.currentPage this.data.totalPages !this.data.loading) { this.setData({ loadMore: true }); this.loadOrders(); } }这里我踩过一个经典坑没有做loading状态判断快速触底会连续触发多次请求导致数据重复或错乱。后来加了一个请求锁在数据回来前忽略所有新的触底事件才解决。分页接口的后端实现我用的是MyBatis-Plus的Page对象返回records、total、pages前端据此计算总页数。列表展示层面还有一个小细节状态对应的标签颜色要统一维护比如待派单用橙色、维修中用蓝色、已完成用绿色。我建了一个状态配置文件页面上直接引用避免在多个页面写死颜色到处不一致。4.4 顶部导航和角色菜单适配小程序默认的顶部导航栏每个页面都要单独配置很容易漏。我在app.json的window里统一设置了导航栏样式个别需要沉浸式的页面再单独覆盖。热词里提到的微信小程序顶部导航栏高度确实是个常见问题因为不同手机的状态栏高度不一样如果要做自定义导航需要通过wx.getSystemInfoSync()获取statusBarHeight再动态计算。角色适配方面我用switchTab加权限判断。首页tab不一定适合所有人所以我没有把全部三个角色堆在同一个tab结构里而是登录后根据role决定跳转到哪个首页再在页面内动态渲染菜单。这个比登录后把所有页面塞在一个tab栏里清晰得多。5. 联调踩坑记版本、跨域、真机差异这部分是全篇的精华。踩过的坑不写出来过两个月我自己都会忘。5.1 SpringBoot版本太高引发的连锁问题我一开始图新鲜直接建了SpringBoot 3.2的项目结果噩梦开始了。首先项目生成默认要求JDK 17而我电脑上同时还有JDK 8的老项目切换起来很头疼。其次SpringBoot 3.x把javax.servlet换成了jakarta.servlet我抄网上老教程里的HttpServletRequest引入直接报错。更坑的是MyBatis-Plus当时的最新版本对SpringBoot 3的支持还不完善折腾了一整天才放弃退回2.7.18。给我的教训是不要为了版本新而选版本。如果你的技术栈是主流的SpringBoot MyBatis-Plus MySQL先确认各自版本之间的兼容矩阵再去创建项目。网上大量教程都基于2.x你选3.x意味着很多代码要自己改对一个做系统的项目来说非常不划算。5.2 本地联调阶段的跨域与请求地址问题小程序端因为不是浏览器环境其实没有浏览器同源策略那一套限制跨域问题主要发生在Web管理端要访问后端的时候。如果你只做小程序端不需要后端配置CORS。但考虑到管理员也可能用电脑网页登录后台我还是加了全局CORS配置允许localhost和指定域名跨域访问放行Authorization请求头。另一个细节是后端的访问地址。小程序开发者工具里本地联调填http://localhost:8080就行但真机预览的时候手机访问不到电脑的localhost。这个阶段的通用做法是让电脑和手机连同一个局域网小程序工具的本地设置里关闭合法域名校验然后请求地址填电脑的局域网IP。注意云服务器部署后要换成HTTPS域名小程序正式线上要求所有请求域名必须配置合法域名且支持HTTPS这是硬性约束不配置就请求失败。5.3 模拟器和真机的灵异事件真机调试时遇到的第一个问题就是模拟器上图片显示正常真机上图片裂了。排查半天发现是我图片上传后返回的URL是http的局域网地址而微信官方对正式版小程序的非HTTPS请求有限制。最终方案是开发阶段用开发者工具的不校验合法域名选项上线前统一换HTTPS的图片地址。第二个真机问题出现在网络请求耗时上。有些页面加载慢模拟器上看不出问题因为电脑网速快真机上4G/5G网络延迟一上来用户会明显感觉到转圈。解决办法是给主要接口加缓存策略比如工单列表用setStorageSync做本地缓存进入页面先展示缓存再请求刷新体验好了很多。5.4 并发和状态冲突多端操作同一工单这个坑发生在联调后期。测试的时候管理员在电脑后台派单维修工正好在手机上同时接单两个人对一个工单进行了不同操作。由于没有加锁两个请求都通过了状态校验最终数据被后写入的覆盖工单状态和实际不符。我的修复方案是在状态流转接口里加数据库层面的条件更新SQL长这样UPDATE repair_order SET status #{newStatus} WHERE id #{id} AND status #{expectStatus}。如果影响行数为0说明状态已被其他操作抢先改变直接提示工单状态已更新请刷新后重试。这类问题用乐观锁的思路解决最合适不用引入Redis分布式锁那么重的方案。6. 如果再给我一个月我会补上的模块项目主体功能已经跑通但作为过来人我个人觉得如果时间允许下面几个方向非常值得扩展。第一个是微信订阅消息。目前用户需要自己刷新页面才能看到维修进度体验一般。微信的订阅消息功能可以让管理员派单后给维修工发消息、完工后给报修人发消息。注意这是一次性订阅用户每次授权只能接收一次消息推送所以要在关键节点引导用户授权。第二个是备件库存管理。维修工在修设备时常遇到没零件的问题如果系统里维护一个简单库存表维修中涉及换件时自动扣减库存库存低于阈值时提醒管理员采购整套系统在后勤处的价值会提升不少。第三个是数据统计和可视化。管理员页面加一张维修趋势图按月展示报修数量、平均维修时长、各楼栋维修频次。这些数据用ECharts就能实现对学校来说是实实在在的管理依据放在毕设答辩里也是亮点。第四个是扫码报修。给每间教室、每个大件设备生成一个二维码标签学生扫一下二维码自动带入位置和设备编号省去手打文字。这个场景在校园里尤其有价值因为报修时最说不清的就是东西在哪。如果时间充裕也可以研究一下SpringBoot整合消息队列比如报修量大时用消息队列做异步通知削峰。不过对桃李园速修这个规模来说现阶段属于锦上添花不必强求。做这套系统最大的体会是技术栈永远是最容易的部分真正难的是把业务规则想明白、把异常路径处理干净。报修工单的状态机定了角色权限边界划清楚前后端协议约好剩下的CRUD其实都是体力活。希望这篇文章能让你少踩几个我踩过的坑尤其是SpringBoot版本那一关——别问我为什么开头没直接劝你选2.7问就是我也年轻过。