尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

SSM+微信小程序设备报修系统:从状态流转到联调排错完整实战

SSM+微信小程序设备报修系统:从状态流转到联调排错完整实战 简介这套基于SSM与微信小程序的设备故障报修管理系统毕业设计项目面向计算机相关专业毕业生和需要快速搭建同类业务的开发者覆盖用户报修、管理员派单、工单状态跟踪、设备信息维护等典型场景。包内包含完整的Java后端源码、MySQL数据库初始化脚本、毕业论文、使用说明文档及演示视频一站式支撑从环境搭建、功能演示到答辩汇报的完整流程。资源共1216个文件压缩包46.16MB主要文件类型涵盖Java业务代码、Vue后台管理页面、微信小程序端wxml/wxss/js逻辑、PNG/JPG界面素材、SQL脚本以及MP4演示录像目录结构直观适合按模块对照学习和二次改造。该项目为个人高分毕业设计答辩评审97分已在Windows10/11环境严格调试并附启动脚本与部署教程下载即可运行也可作为课程设计或新系统开发的基础原型。目前已有184人学习下载对希望掌握SSM框架与小程序前后端联调的读者能提供可直接复用的代码结构和论文写作范本。1. 设备故障报修管理系统难点不在表单而在状态流转设备报修这个场景表面上是“填一张故障单 后台有人处理”很多同学一开始把精力全放在表单 UI 和增删改查上做出来却总在答辩时被问住。问题不在页面不够好看而在报修单从提交、受理、处理到评价的状态流转没有闭环谁接单、处理到哪一步、报修人能看到什么、超时怎么办这些才是系统真正要解的问题。这套基于 SSM 微信小程序的设备故障报修管理系统用户端是微信小程序后端用 SSM 框架承接管理后台和接口服务典型的数据流是“小程序提交报修 → SSM 写入数据库 → 管理员/维修员在后台处理 → 状态回写 → 小程序端展示进度”。适合两类读者正在找毕业设计参考的应届生以及想给单位做内部报修工具、又不想上重型 OA 的后端工程师。后面的内容我会从后端分层、表结构、接口设计、小程序端封装一直讲到答辩排错全程是可复现的写法。2. 先拆 SSM 后端三层Controller 只做转发业务留在 Service2.1 为什么毕设场景仍多选 SSM 而非 Spring Boot现在 Java 面试题里 Spring Boot 基本是默认技能但高校课程和大量毕设参考资料仍然以 SSM 为主线。原因不难理解SSM 的三层边界更明显SpringMVC 的请求映射、Spring 的声明式事务、MyBatis 的 SQL 控制都是显式配置能完整对应“表现层 - 业务层 - 持久层”的教学框架。用 SSM 做毕设不丢分关键是能说清楚每一层在做什么。Spring Boot 把配置自动化的同时也把很多边界藏了起来对需要现场讲原理的答辩反而不利。后端工程我习惯按下面的包结构拆这个结构也方便后面写论文里的架构图com.example.repair ├── controller // 接收请求、参数校验、返回 JSON ├── service // 业务逻辑、事务边界 ├── dao // MyBatis Mapper 接口 ├── entity // 数据库实体 ├── common // 统一返回对象、异常、状态常量 └── interceptor // 登录拦截、Token 校验2.2 Controller-Service-Mapper 三层包结构报修单实体字段一次定对很多同学写 Service 时喜欢把所有逻辑堆到一个方法里导致一个接口几百行。这里给出一个状态流转的标准拆分Controller 只接收参数和调用 Service把校验交给 ServiceService 内部先查旧状态再判合法性最后更新Mapper 只处理单表写操作。以“维修员受理报修单”为例Override Transactional(rollbackFor Exception.class) public void acceptOrder(Long orderId, Long handlerId) { RepairOrder update new RepairOrder(); update.setId(orderId); update.setHandlerId(handlerId); update.setStatus(OrderStatus.PROCESSING); update.setHandleTime(new Date()); int rows repairOrderMapper.updateStatusById(update, OrderStatus.WAITING); if (rows 0) { throw new BizException(单据已被处理请刷新后重试); } repairLogMapper.insert(RepairLog.create(orderId, handlerId, 受理报修单)); }这段代码有三个关键点一是Transactional保证状态更新和日志写入同时成功或同时失败避免出现“状态改了但没记录”的脏数据二是 update 语句的 where 条件里带上“期望的当前状态”相当于乐观锁三是通过rows 0判断是否冲突如果有并发操作第二个请求会拿到 0 行更新结果直接提示用户刷新不再往下走。对应的 Mapper XML 写法如下update idupdateStatusById UPDATE repair_order SET status #{param.newStatus}, handler_id #{param.handlerId}, handle_time #{param.handleTime} WHERE id #{param.id} AND status #{expectStatus} /updateexpectStatus是调用方传入的期望前置状态这个字段的命名和类型最好在 Mapper 接口里用Param显式声明避免多个参数时 MyBatis 用arg0、arg1导致 SQL 解析错误。下面用表格把报修单的状态机理清楚后续所有接口都围绕这个表来设计操作前置状态目标状态关联动作操作人提交报修无0 待受理写 repair_log用户受理工单0 待受理1 处理中指派维修员管理员完成处理1 处理中2 已完成写结单备注维修员用户确认2 已完成3 已评价写评分与意见用户撤销报修0 待受理4 已撤销记录原因用户状态字段用int比用varchar更省空间配合后端常量类管理可读性也够。开发阶段为了调试方便我给这个 int 字段加过TableField注释并在实体类上用JsonFormat控制时间输出避免前端拿到一串时间戳。2.3 状态流转的 Service 实现与 MyBatis SQL 写法上面的 acceptOrder 只是第一步完整的“完成工单”接口会再带上图片和备注。我一般会给 repair_order 增加complete_remark和finish_time两个字段更新时把备注写进去同时往 repair_log 插记录。这里不新建一条报修单而是更新现有记录这是和“追加记录”最大的区别报修单主表永远只保留一份当前状态历史轨迹全部放日志表。如果后期要做“处理超时提醒”也只查主表的handle_time和当前状态。2.4 SpringMVC 接收小程序参数3 个必踩的坑小程序端的wx.request默认以 JSON 格式提交后端如果写成下面这样就必须保证请求头带Content-Type: application/jsonPostMapping(/api/order/add) ResponseBody public ResultVO addOrder(RequestBody RepairOrderVO vo) { return ResultVO.success(repairService.create(vo)); }第一个坑是参数类型不匹配小程序端传的handler_id如果是字符串MyBatis 在写入数据库时可能报数字转换异常解决方式是在前端提交前统一转数字或者在 VO 里用 String 接收再手动校验转换。第二个坑是时间字段前端传2025-01-01 10:00:00这种字符串后端直接用 Date 接收会报 400可以在 VO 里定义成 String进入 Service 后用DateUtil.parse显式转换错误信息更可控。第三个坑是字段命名Java 的驼峰和数据库下划线对应关系要在mybatis-config.xml里开启map-underscore-to-camel-case否则createTime永远映射不上create_time。3. 数据库设计设备、报修单、日志三张核心表3.1 从数据库课程设计的角度拆三张核心表与建表 SQL需要先明确一个基本判断设备信息、报修单、处理日志必须分开建表不能把设备信息冗余到报修单里。从数据库课程设计的角度来看这对应的是“一对多”关系一台设备可以有多条报修记录一条报修记录又对应多条处理日志并且设备的位置、编号变化不能影响历史报修单的展示所以报修单里只存device_id不直接冗余设备地址。三张核心表的职责见下表表名职责关键字段关联关系device设备台账id, device_code, device_name, location被报修单引用repair_order报修单主表id, order_no, device_id, reporter_id, status关联 device、userrepair_log处理日志表id, order_id, operator_id, action, remark多对一关联 repair_order建表 SQL 用 InnoDB 引擎字符集统一utf8mb4因为用户在小程序端填写的故障描述可能包含 emoji 表情直接用utf8会出现无法入库或乱码。下面是设备表和报修单表的精简版本CREATE TABLE device ( id BIGINT PRIMARY KEY AUTO_INCREMENT, device_code VARCHAR(32) NOT NULL UNIQUE, device_name VARCHAR(64) NOT NULL, location VARCHAR(128) DEFAULT NULL, status TINYINT DEFAULT 1 COMMENT 1正常 0维修中, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE InnoDB DEFAULT CHARSET utf8mb4; CREATE TABLE repair_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE, device_id BIGINT NOT NULL, reporter_id BIGINT NOT NULL, handler_id BIGINT DEFAULT NULL, description VARCHAR(500) NOT NULL, images VARCHAR(1000) DEFAULT NULL COMMENT 逗号分隔图片URL, status TINYINT DEFAULT 0 COMMENT 0待受理 1处理中 2已完成 3已评价 4已撤销, priority TINYINT DEFAULT 1 COMMENT 1普通 2紧急, handle_time DATETIME DEFAULT NULL, finish_time DATETIME DEFAULT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_device_id (device_id), KEY idx_status (status) ) ENGINE InnoDB DEFAULT CHARSET utf8mb4;注意两个索引idx_status是为了支撑“待受理列表”这种高频查询order_no的唯一索引是配合后面的单号生成策略。如果后期报修数量大status 区分度不够可以改成idx_status_create_time复合索引让列表页按状态和时间同时过滤。字段注释要写清楚状态枚举的含义因为论文里需要贴表结构说明注释直接复制过去就能用。3.2 报修单号生成规则日期序列与唯一索引兜底不建议把自增主键 id 直接展示给用户单号要用有业务含义的格式。常见做法是日期加当天序号BX20250101001含义是报修单、2025-01-01、当天第 1 单。生成逻辑先查当天已有单数加一后拼接然后插入时依赖order_no唯一索引兜底。SELECT COUNT(*) FROM repair_order WHERE order_no LIKE CONCAT(BX, DATE_FORMAT(NOW(), %Y%m%d), %)如果并发请求同时查到这个 count后插入的那条会因唯一索引报错此时重试一次即可。对于毕设量级这样完全够用不需要引入号段或雪花算法。把这段逻辑放在 Service 里不要放在 Mapper 或 Controller因为单号生成属于业务规则。3.3 待受理列表的联表查询 SQL维修员打开后台看到的第一屏是待受理列表这个列表至少要显示单号、设备名称、位置、报修人、故障描述、提交时间。最直接的 SQL 是三表联查SELECT r.order_no, d.device_name, d.location, u.nickname AS reporter, r.description, r.create_time, r.priority FROM repair_order r LEFT JOIN device d ON r.device_id d.id LEFT JOIN user u ON r.reporter_id u.id WHERE r.status 0 ORDER BY r.priority DESC, r.create_time ASC LIMIT #{offset}, #{pageSize};排序用priority DESC, create_time ASC紧急单排前面同优先级按提交时间从早到晚正好对应“先来先处理”的业务规则。查询接口的两个分页参数需要做上限控制pageSize 不超过 50防止小程序端一次性拉太多数据导致渲染卡顿。另外轻量“我的报修”列表只需要按 reporter_id 过滤加一个idx_reporter_id索引即可这里不再单独贴 SQL。3.4 微信小程序登录code 换 openid 与 Token 鉴权小程序的登录流程和传统账号密码不同前端调用wx.login()获取临时 code后端拿 code 换 openid 和 session_key。openid 是用户在小程序内的唯一标识不能从小程序端直接获取必须走后端交换接口String url https://api.weixin.qq.com/sns/jscode2session; MapString, String params new HashMap(); params.put(appid, APPID); params.put(secret, SECRET); params.put(js_code, code); params.put(grant_type, authorization_code); String result HttpUtil.get(url, params); // 返回 openid 和 session_key这里有一个非常重要的边界secret 只能存在后端绝对不能写进小程序代码否则任何人反编译源码就能拿到你的小程序密钥。获取 openid 之后去 user 表查是否存在该用户不存在就先按昵称“微信用户”自动注册然后生成一个随机 token 返回给前端。后续请求都带这个 token后端用拦截器校验校验通过后再把 userId 放到 ThreadLocal 或 Request 属性里Controller 随时可取。Token 的有效期建议设成 7 天到期后小程序重新走一次 wx.login 就能续期。4. 微信小程序端报修入口、图片上传、状态可见4.1 小程序目录结构与 app.json 基础配置小程序端的页面建议按角色拆分用户看到首页报修列表、提交报修、我的报修、详情四个页面管理员和维修员尽量复用同一套页面通过隐藏不同按钮来控制操作权限避免页面数量膨胀。目录结构如下├── app.js // 全局登录逻辑 ├── app.json // 页面注册、窗口配置 ├── utils/request.js // 请求封装 ├── pages/ │ ├── login/ // 登录页 │ ├── index/ // 报修列表首页 │ ├── report/ // 提交报修 │ ├── detail/ // 工单详情 │ └── mine/ // 我的报修 └── components/ // 空状态、工单卡片等组件app.json 里要重点配置两处navigationBarTitleText 按页面设置方便区分功能如果打算自定义顶部导航就把 navigationStyle 设为 custom然后通过wx.getWindowInfo().statusBarHeight计算状态栏高度做安全区适配。很多手机顶部有刘海适配不对就会遮挡自定义标题这部分是答辩时容易被点名的细节。如果对原生小程序的组件样式不熟也可以把页面直接迁移到 uni-app 微信小程序用标签化组件和条件编译处理request 封装的思路一致只是把wx.request换成uni.request。4.2 封装 wx.request统一 baseURL、token 与错误码处理所有页面请求都应该走同一个封装函数避免每个页面重复写wx.request和错误处理。下面是实际可用的 request.js 核心逻辑const BASE_URL http://localhost:8080/repair const request (url, method GET, data {}) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL url, method, data, header: { Content-Type: application/json, token: wx.getStorageSync(token) }, success: (res) { if (res.data.code 200) { resolve(res.data.data) } else if (res.data.code 401) { wx.removeStorageSync(token) wx.redirectTo({ url: /pages/login/login }) } else { wx.showToast({ title: res.data.msg || 请求失败, icon: none }) reject(res.data) } }, fail: (err) reject(err) }) }) } module.exports { request, BASE_URL }这里做了三件事把 baseURL 集中管理改后端地址只需要动一处把 token 统一加到请求头对 401 做全局拦截直接跳回登录页而不是让每个页面自己判登录失效。开发调试时注意微信开发者工具的“不校验合法域名”开关只影响模拟器真机预览时 BASE_URL 必须换成局域网 IP 或者已备案的 HTTPS 域名且需要在 mp 后台配置 request 合法域名这是无法跳过的配置项。接口联调阶段如果发现提交的数据后端收不到优先打开开发者工具的 Network 面板看 Content-Type 是不是被小程序自动改成了表单格式。4.3 报修表单、单选框与图片上传提交报修页最核心的两个能力是故障类型选择和图片上传。故障类型用 radio-group 实现可选项和设备类型联动的部分写死在页面 data 里即可图片上传必须用wx.uploadFile而不是wx.request因为前者专门处理 multipart/form-data 类型wx.chooseMedia({ count: 3, mediaType: [image], sourceType: [camera, album], success: (res) { const filePath res.tempFiles[0].tempFilePath wx.uploadFile({ url: BASE_URL /api/upload, filePath, name: file, header: { token: wx.getStorageSync(token) }, success: (uploadRes) { const data JSON.parse(uploadRes.data) // data.url 为后端返回的图片访问地址 this.setData({ imageUrls: [...this.data.imageUrls, data.url] }) } }) } })count 最多 3 张是为了控制后端存储压力和报修单展示长度。上传成功后再点“提交报修”走普通的 request 请求把 imageUrls 用逗号拼接成一个字符串存进repair_order.images字段。这里需要注意时序必须等图片全部上传完成再提交表单否则报修单里没有图片证据。上传失败时后端要返回明确的错误信息小程序端用wx.showToast提示用户重新选择而不是静默丢图。真机调试时如果 uploadFile 一直失败先确认后端 upload 接口是否已经加入请求白名单因为上传接口通常也不带 token。4.4 工单列表渲染与状态轮询报修单列表页的状态文案不能直接显示数字需要在前端做映射。这里给出一个可复用的映射方案const STATUS_MAP { 0: { text: 待受理, color: orange }, 1: { text: 处理中, color: blue }, 2: { text: 已完成, color: green }, 3: { text: 已评价, color: gray }, 4: { text: 已撤销, color: gray } }同时要处理好工单卡片的数据来源。常见做法是首次加载用列表接口拉 20 条数据下拉触发onPullDownRefresh重新拉第一页触底用onReachBottom加载下一页。对于“处理中”的工单我一般加一个 10 秒的轮询定时器只刷新当前页数据页面通过onHide清除定时器避免切后台后继续发请求。轮询接口用status 1过滤比全量刷新的接口省流量也让用户能尽快看到维修员更新后的状态。列表渲染时注意用wx:key绑定 orderNo 而不是 index否则状态更新后列表重排会引起渲染异常。5. 从跑通到高分联调排错与答辩演示该怎么准备5.1 联调排错从 Network 面板看到 404/400 的处理开发过程中遇到的接口问题九成能从请求面板找到答案。拿到一个接口报错按下面的顺序排查先看请求 URL 里的 IP、端口、路径是否和后端一致SSM 项目部署在 Tomcat 时默认端口是 8080路径中通常要带项目名再看请求方式是 GET 还是 POST后端RequestMapping没指定 method 时两者都接收但GetMapping和PostMapping会严格校验最后看请求体后端用RequestBody接收时必须传 JSON 字符串用param接收时必须传表单格式。遇到 400 先检查字段名和前端 data 里的 key 是否一致遇到 500 则直接看后端 Tomcat 控制台堆栈MyBatis 的 SQL 报错一般都能直接看到具体字段。关键排错项可以记在下表里现象检查点解决方向请求 404路径、项目名、Tomcat 端口核对 BASE_URL 与后端 context-path请求 400Content-Type 与字段类型统一 application/json检查时间字段列表数据慢SQL 是否全表扫描看 explain 结果补联合索引图片传不上uploadFile 路径与磁盘权限确认上传目录存在且有写权限5.2 答辩演示顺序与数据库设计问答点演示的时候不要从登录页开始慢慢点直接按三条路径切第一条是用户提交带图片的报修单展示提交成功后列表里出现“待受理”状态第二条是管理员在后台点受理回到小程序端展示状态变更为“处理中”同时能看到处理时间被写入第三条是维修员完成工单用户在详情页看到“已完成”和评价入口。这条路径完整覆盖状态机的四个关键节点比零散点按按钮更有说服力。数据库部分重点准备三块表之间怎么保持关联为什么状态历史要拆日志表而不是直接改主表order_no 唯一索引起什么作用。打开演示视频录制时hahaha 分别按这三条路径各录一段配上简单字幕说明数据变化论文中使用截图时直接截取管理后台和设备表结构。5.3 一个低成本亮点功能催单 日志表闭环如果代码量和时间都还有富余优先做一个“催单”功能。业务逻辑不复杂当报修单状态为“处理中”且已超过两小时用户端详情页显示催单按钮点击后调用后端接口在 repair_log 表插入一条“用户催单”记录同时更新时间字段。这个功能代码量不大但是能在答辩时展示你对业务细节的思考——它用到了日志表、状态判断和权限校验三个点正好对应论文里的核心设计。实现时只需要在 RepairLog 表增加action字段值为 remind 时前端展示特殊时间线图标。这个小功能比单纯多做一个 Excel 导出更能体现系统设计的完整性。本文还有配套的精品资源点击获取
返回列表