简介这是一套面向高校计算机相关专业毕业生的设备故障报修管理系统完整项目采用SSM框架搭配微信小程序实现适合作为毕业设计、课程设计或Java全栈练手案例。项目已通过导师指导与答辩评审获得97分成绩在Windows 10/11环境下严格调试下载即可运行。压缩包共1216个文件约46.16MB包含117个Java后端源码、123个Vue前端组件、170个JavaScript脚本、81个WXML与83个WXSS小程序页面文件以及2个SQL数据库脚本、论文文档、使用说明与演示视频另附PNG、SVG、JPG等界面素材与配置文件结构完整、层次清晰。目前已有184人学习下载。读者可获得一套可直接部署的高分项目方案涵盖后端接口、小程序端页面、数据库设计与论文写作参考配合演示视频与部署文档能快速理解设备报修流程的实现思路也可作为二次开发与答辩准备的基础素材。1. 从一份“能跑起来”的毕设说起SSM微信小程序设备报修系统到底在做什么很多同学做毕设时最怕的不是写代码而是“写完跑不起来、答辩讲不清楚、老师问数据库怎么设计的直接卡壳”。设备故障报修管理系统这个题目恰好踩中了三个高频需求一是业务闭环清晰从报修、派单、维修到验收每一步都有状态流转二是技术栈主流后端用 SSMSpring SpringMVC MyBatis前端用微信小程序数据库用 MySQL答辩时老师一听就知道你确实动手了三是可演示性强小程序扫码就能打开比纯 Web 后台更容易在答辩现场出效果。这篇笔记不聊虚的就按“一个真实可交付的毕设项目”来拆SSM 后端怎么分层、小程序怎么调接口、数据库表怎么设计、报修状态机怎么落地、以及那些让无数人翻车的配置细节。适合正在选毕设题目的同学也适合已经选了题但卡在“跑不通”阶段的开发者。2. SSM 后端分层与微信小程序对接从建表到接口跑通2.1 为什么选 SSM 而不是 SpringBoot 做毕设先说选型理由。SSM 和 SpringBoot 都能做这个系统但毕设场景下 SSM 有三个现实优势第一答辩时老师对 SSM 的接受度极高XML 配置和注解混用的方式能体现你对 Spring 容器、AOP、事务管理的理解而 SpringBoot 自动装配容易让老师觉得“你只是会写 Controller”第二SSM 的 Mapper XML 写 SQL 更直观设备报修系统里大量涉及多表关联查询比如查某个维修工的所有待处理工单手写 SQL 比 MyBatis-Plus 的链式调用更容易讲清楚第三很多学校的毕设模板就是 SSM环境配置有现成参考减少“环境都搭不起来”的风险。当然SSM 的代价是配置繁琐。我一般会建议把配置文件拆成三份applicationContext.xml管 Spring 容器和事务spring-mvc.xml管 Controller 扫描和视图解析mybatis-config.xml管 MyBatis 全局设置。这样出问题时能快速定位是容器没起来还是映射没加载。2.2 数据库表设计五张核心表撑起报修闭环设备报修系统的表不用多但状态字段和关联字段必须设计清楚。下面是我在实际项目中反复调整后的最小可用表结构表名作用关键字段user用户表学生/教师/维修工/管理员id, openid, name, role, phonedevice设备表id, device_no, name, location, statusrepair_order报修工单主表id, device_id, user_id, fault_desc, status, create_timerepair_log工单流转记录id, order_id, operator_id, action, remark, create_timenotice通知表id, user_id, content, is_read, create_time其中repair_order.status是整个系统的核心我一般用 0-4 表示0 待派单、1 已派单、2 维修中、3 待验收、4 已完成。这个状态机后面会专门讲。建表时有个血泪经验openid字段一定要加唯一索引否则同一个微信用户重复授权会插入多条记录导致登录后查到的工单列表错乱。另外create_time用datetime而不是timestamp避免时区问题在小程序端显示成“8 小时前”。2.3 后端接口分层Controller 只做参数校验Service 管状态流转SSM 的分层不是摆设。我见过太多毕设把业务逻辑全写在 Controller 里结果答辩时老师问“如果维修工拒单怎么处理”代码里根本找不到对应逻辑。正确的做法是// RepairOrderController.java RestController RequestMapping(/api/order) public class RepairOrderController { Autowired private RepairOrderService repairOrderService; PostMapping(/create) public Result createOrder(RequestBody RepairOrderDTO dto) { // 只做参数校验不写业务 if (dto.getDeviceId() null || dto.getFaultDesc() null) { return Result.error(设备ID和故障描述不能为空); } return repairOrderService.createOrder(dto); } PostMapping(/assign) public Result assignOrder(RequestParam Integer orderId, RequestParam Integer workerId) { return repairOrderService.assignOrder(orderId, workerId); } }Service 层才是状态流转的战场// RepairOrderServiceImpl.java Service Transactional public class RepairOrderServiceImpl implements RepairOrderService { Autowired private RepairOrderMapper orderMapper; Autowired private RepairLogMapper logMapper; Override public Result assignOrder(Integer orderId, Integer workerId) { RepairOrder order orderMapper.selectById(orderId); // 只有待派单状态才能派单 if (order.getStatus() ! 0) { return Result.error(当前工单状态不允许派单); } order.setStatus(1); order.setWorkerId(workerId); orderMapper.updateById(order); // 记录流转日志 logMapper.insert(new RepairLog(orderId, workerId, 派单, 管理员派单)); return Result.success(派单成功); } }这段代码的关键在于状态判断放在 Service 里并且用Transactional保证更新工单和写日志要么都成功要么都失败。参数说明orderId是工单主键workerId是维修工的用户 IDstatus的合法值在 Service 里硬编码校验不要依赖前端传。2.4 微信小程序端请求封装与登录态保持小程序端最容易翻车的地方是请求封装和登录态。很多同学直接在每个页面写wx.request结果 token 过期了都不知道怎么统一处理。我一般会封装一个request.js// utils/request.js const BASE_URL http://localhost:8080/api; function request(options) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL options.url, method: options.method || GET, data: options.data || {}, header: { content-type: application/json, token: wx.getStorageSync(token) || }, success(res) { if (res.data.code 401) { // token 失效重新登录 wx.removeStorageSync(token); wx.redirectTo({ url: /pages/login/login }); reject(new Error(登录已过期)); } else { resolve(res.data); } }, fail(err) { reject(err); } }); }); } module.exports { request };逻辑说明所有请求走统一入口token 从本地缓存读取后端返回 401 时自动跳登录页。参数说明BASE_URL在开发阶段用 localhost上线前要改成服务器地址token是后端登录接口返回的 JWT 或自定义令牌存在wx.setStorageSync里。登录流程我一般这样设计小程序调用wx.login拿到 code传给后端换 openid后端查user表如果不存在就自动注册然后生成 token 返回。注意wx.login的 code 只能用一次不要缓存。3. 报修状态机与工单流转把“派单-维修-验收”串成一条线3.1 状态机设计五个状态和四条流转规则设备报修系统的核心不是增删改查而是状态流转。我见过最离谱的毕设是工单状态可以随便改前端传什么就存什么答辩时老师问“如果维修工还没修完学生能不能直接验收”代码里完全没有校验。正确的状态机应该是0 待派单学生提交报修后进入此状态只有管理员能操作1 已派单管理员指派维修工后进入此状态维修工可接单2 维修中维修工点击“开始维修”后进入此状态3 待验收维修工点击“维修完成”后进入此状态学生可验收4 已完成学生确认验收后进入此状态工单关闭流转规则只有四条0→1派单、1→2开始维修、2→3维修完成、3→4验收通过。任何其他跳转都应该被拒绝。实现方式是在 Service 层写一个canTransfer方法private boolean canTransfer(int from, int to) { if (from 0 to 1) return true; if (from 1 to 2) return true; if (from 2 to 3) return true; if (from 3 to 4) return true; return false; }每次状态变更前先调用这个方法不合法就抛异常。这样即使前端被篡改后端也能兜住。3.2 工单列表的多角色查询一条 SQL 搞定四种身份系统里有四种角色学生、维修工、管理员、教师。不同角色看到的工单列表完全不同。学生只看自己的报修维修工只看派给自己的管理员看全部。如果每个角色写一个接口代码会膨胀得很快。我一般用一条 SQL 加动态条件!-- RepairOrderMapper.xml -- select idselectByRole resultTypeRepairOrderVO SELECT o.*, d.name AS deviceName, u.name AS userName FROM repair_order o LEFT JOIN device d ON o.device_id d.id LEFT JOIN user u ON o.user_id u.id where if testrole student AND o.user_id #{userId} /if if testrole worker AND o.worker_id #{userId} /if if teststatus ! null AND o.status #{status} /if /where ORDER BY o.create_time DESC /select参数说明role从当前登录用户信息里取不要从前端传userId是当前用户 IDstatus是可选筛选条件。这样一条 SQL 覆盖所有角色的列表查询Service 层只需要根据角色传不同参数。3.3 维修工接单与拒单并发场景下的简单处理毕设系统一般不考虑高并发但“两个维修工同时接同一单”这种场景还是会被老师问到。最简单的处理方式是在更新时加状态条件UPDATE repair_order SET status 2, worker_id #{workerId} WHERE id #{orderId} AND status 1然后判断affectedRows如果返回 0 说明工单已经被别人接走了。这种方式不需要锁表也不需要分布式锁对毕设来说足够。参数说明status 1是乐观锁条件确保只有待接单状态才能被更新。3.4 消息通知小程序订阅消息的降级方案微信小程序的订阅消息需要用户主动授权而且模板审核比较麻烦。毕设阶段我一般建议用“站内通知”代替工单状态变更时往notice表插一条记录小程序端轮询未读数量。这样不依赖微信审核演示时也能看到效果。// 状态变更后插入通知 private void sendNotice(Integer userId, String content) { Notice notice new Notice(); notice.setUserId(userId); notice.setContent(content); notice.setIsRead(0); notice.setCreateTime(new Date()); noticeMapper.insert(notice); }参数说明userId是接收通知的用户 IDcontent是通知内容isRead默认 0 表示未读。小程序端在onShow里调一次未读数量接口红点提示即可。4. 避坑与排查那些让毕设“跑不起来”的细节4.1 跨域问题小程序开发工具不报错真机报错现象微信开发者工具里请求正常真机预览时所有接口失败。原因开发者工具默认关闭了跨域校验真机走的是真实网络请求后端没配 CORS。解决在 SpringMVC 配置里加CrossOrigin或者全局 CORS 配置Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOrigins(*) .allowedMethods(GET, POST, PUT, DELETE) .allowedHeaders(*); } }注意allowedOrigins(*)在 Spring 5.3 之后需要配合allowCredentials(false)否则会报错。4.2 MyBatis 驼峰映射失效查出来的字段全是 null现象数据库字段是device_no实体类属性是deviceNo查询结果里deviceNo一直是 null。原因MyBatis 默认不开启驼峰映射。解决在mybatis-config.xml里加settings setting namemapUnderscoreToCamelCase valuetrue/ /settings或者用resultMap手动映射。我一般直接开全局配置省事。4.3 小程序登录态丢失token 存了但请求没带上现象登录成功后跳转首页首页请求报 401。原因wx.setStorageSync是同步的但页面跳转后onLoad里读缓存可能拿到空值。解决在app.js的onLaunch里先读一次 token 存到全局变量请求封装里优先从全局变量取取不到再读缓存。另外注意wx.request的 header 里 token 字段名要和后端一致大小写敏感。4.4 工单状态更新后列表不刷新现象维修工点击“开始维修”状态更新成功但返回列表页还是旧状态。原因小程序页面栈里列表页没有重新加载。解决在onShow里重新调列表接口而不是只在onLoad里调。或者用wx.navigateBack时在上一页的onShow里刷新。我一般直接在列表页的onShow里加刷新逻辑简单可靠。4.5 数据库连接超时演示到一半报错现象答辩演示时系统放了半小时没操作再点按钮报“连接超时”。原因MySQL 默认wait_timeout是 8 小时但连接池比如 Druid的maxEvictableIdleTimeMillis可能更短。解决在连接池配置里加validationQuery: SELECT 1和testWhileIdle: true让连接池定期检测空闲连接。另外演示前重启一次后端服务确保连接池是热的。5. 进阶技巧用日志和接口文档把毕设变成“可维护项目”5.1 用 AOP 统一记录操作日志毕设答辩时老师经常问“你怎么知道谁改了工单状态”。如果每次都在 Service 里手写日志代码会很乱。我一般用 AOP 切Log注解Aspect Component public class LogAspect { Autowired private RepairLogMapper logMapper; Around(annotation(logAnnotation)) public Object around(ProceedingJoinPoint joinPoint, Log logAnnotation) throws Throwable { Object result joinPoint.proceed(); // 从参数里取 orderId 和 operatorId Object[] args joinPoint.getArgs(); // 省略参数解析实际项目里根据方法签名取 logMapper.insert(new RepairLog(/* orderId, operatorId, action */)); return result; } }这样在 Service 方法上加Log(action 派单)就能自动记录参数说明action是操作描述orderId和operatorId从方法参数里解析。注意 AOP 只对 Spring 代理的方法生效Controller 内部调用 Service 没问题但 Service 内部自调用不会触发。5.2 用 Swagger 生成接口文档答辩时直接展示SSM 集成 Swagger 稍微麻烦一点但效果很好。加依赖后在spring-mvc.xml里配bean classspringfox.documentation.swagger2.configuration.Swagger2DocumentationConfiguration/ mvc:resources mapping/swagger-ui.html locationclasspath:/META-INF/resources//然后 Controller 上加Api和ApiOperation注解。答辩时打开http://localhost:8080/swagger-ui.html所有接口一目了然老师会觉得你工程化意识不错。注意 Swagger 2 和 Spring 5 有兼容性问题如果报错就换 Springfox 3.0.0。5.3 一个我反复用的调试习惯每次改完 Mapper XML 或 Service 逻辑不要直接重启整个 Tomcat。我一般用 JRebel 或者 IDEA 的热部署改完等几秒自动生效。如果热部署失效先看target/classes下的 XML 有没有更新很多时候是编译没同步。另外所有接口先用 Postman 调通再写小程序端这样能快速区分是后端问题还是前端问题。这个习惯帮我省了至少一半的联调时间。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?