简介:一套面向毕业设计场景的宿舍报修系统小程序完整项目方案,包含微信小程序端、SpringBoot后端与MySQL数据库脚本,适合计算机相关专业学生用于毕设参考或前后端开发练习。压缩包共744个文件,大小43.59MB,文件构成覆盖开发全链路:161个svg与125个png提供界面素材,128个vue及22个wxml、23个wxss构建前端页面,101个java处理后端业务,66个js、30个json承担交互与配置,sql为数据库初始化脚本,另有mp4视频教程与论文相关材料。包内还附有1-install.bat、2-run.bat、3-build.bat一键脚本,便于快速安装启动。该项目针对宿舍报修管理中信息混乱、效率低等问题,集中管理报修工单、处理状态与反馈记录,可帮助理解小程序与SpringBoot前后端分离架构及MySQL表设计。目前已有262人学习下载,从零搭建毕设项目可直接使用完整源码与配套论文视频。
1. 宿舍报修系统小程序:一个毕设标题背后的完整技术链路
“宿舍报修系统小程序”这句标题,在毕业设计选题里出现频率相当高。你大概率见过宿舍楼管阿姨手里那个写满「灯管坏」「下水道堵」的登记本:问题记录得潦草,维修进度全靠催,学生不知道有没有人管,维修工跑空趟更是家常便饭。这个题目要做的,就是把这条手工链路搬到微信里:学生小程序提交报修单,管理员后台分配维修工,维修工上门处理完更新状态,学生确认并评价。标题里“微信小程序 + SpringBoot + MySQL”三个词,恰好就是当下最主流的“前后端分离”入门组合,代码量适中、论文结构清晰、答辩演示直观,所以它成了毕设常青树。这篇文章我按自己实际带项目的路线来讲,先把三层架构各管什么事说清楚,再一步步把工程跑起来,随后落到报修、上传、通知这些核心流程,最后把容易翻车的坑点挨个点名。
2. 三层结构拆解:小程序、SpringBoot、MySQL 各自的责任边界
很多同学拿到源码后第一件事就是打开微信开发者工具点了“编译”,看到报错后又去启动后端,两头折腾半天才跑起来,原因就是没先分清这三层分别解决什么问题。这一章先做静态拆解,把项目看成一栋房子的图纸再进门。
2.1 微信小程序端:页面、组件与 request 封装
小程序端本质上是一个“前台收银台”,负责展示数据和采集用户操作,几乎不应该放业务规则。我见过有同学把状态判断写死在页面里,比如判断报修单状态时直接比较数字“0、1、2”,结果后端状态意义一调整,前端逻辑全部作废。这里的原则是:小程序只做三件事——渲染、录入、转发。
页面结构一般是:首页放报修入口 + 公告轮播,报修提交页选择分类、填描述、传图片,报修列表页分页展示自己的工单,详情页看处理进度和维修工留言,个人中心管资料和登录状态。如果你拿到的源码里没有用组件化写法,我建议保留它原本的风格,毕设阶段不必强行重构。
所有请求走一个统一封装,这是第一个值得留意的代码点:
// utils/request.js const BASE_URL = 'http://localhost:8080/api'; // 本地联调地址,真机预览要换成电脑局域网 IP function request(url, method, data, needAuth = true) { return new Promise((resolve, reject) => { wx.request({ url: BASE_URL + url, method: method || 'GET', data: data || {}, header: needAuth ? { 'Authorization': wx.getStorageSync('token') || '' } : {}, success(res) { if (res.data.code === 200) { resolve(res.data.data); } else { reject(res.data); } }, fail(err) { reject(err); } }); }); } module.exports = { request, BASE_URL };逻辑说明:wx.request原本是基于回调的写法,包成 Promise 之后页面里就能用async/await写同步风格,代码干净得多。Authorization头是登录后存储在本地的 token,后端靠它识别当前用户。
参数说明:BASE_URL是整个项目最容易改错的地方。本地联调用localhost没问题,但手机真机调试时,手机访问localhost会找到自己而不是电脑,必须改成电脑的局域网 IP,比如http://192.168.1.100:8080/api。这个问题后面避坑章节还会再点一次名。
2.2 SpringBoot 后端:分层、依赖与配置
后端按习惯拆成 Controller(接请求)、Service(写业务)、Mapper(操作 MySQL)三层。实体类用 JavaBean 配合 MyBatis-Plus,能省掉大量手写 SQL 的重复劳动。这里先给出一组最稳妥的依赖选择:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> </dependencies>逻辑说明:spring-boot-starter-web内置 Tomcat 和 Spring MVC;MyBatis-Plus 把单表 CRUD 简化成直接调用现成方法,不必手写XXXMapper.xml;Lombok 用注解批量生成 getter/setter;validation 依赖负责参数校验。这套组合是当前毕设里最不容易出错的配置。
选型理由:为什么推荐 MyBatis-Plus 而不是原生 MyBatis?时间有限,重点应放在报修业务的流转上,而不是花大量时间写基本的增删改查 SQL。MyBatis-Plus 的save()、selectById()等内置方法足够覆盖绝大多数场景,等答辩时老师问起底层原理,能答出“它在 MyBatis 上加了通用 Mapper 实现”就已经合格了。
2.3 MySQL 端:表结构设计与报修单状态机
看一个毕设源码,先看数据库脚本,往往就能判断出这个项目作者的工程能力。宿舍报修系统核心表大致是这几张:
| 表名 | 核心字段 | 设计意图 |
|---|---|---|
| student | id, student_no, name, phone, dorm_building, dorm_room, openid | 学生档案,openid 关联小程序身份 |
| repair_order | id, order_no, student_id, category, description, images, status, priority, repair_man_id, repair_man_remark, create_time, update_time, finish_time | 报修主流程 |
| repair_man | id, name, phone, skill_type, status | 维修工信息 |
| admin_user | id, username, password, role | 管理员账号,密码需要做加密 |
| notice | id, title, content, create_time | 宿舍公告 |
报修单的status字段是整个系统的灵魂,常见状态机是:0 待受理 → 1 维修中 → 2 已完成 → 3 已评价,或者用户在待受理时主动取消变成 -1。为什么要单独设计order_no而不是直接用自增主键?因为自增 id 容易被外部遍历到,你传一个 id 到接口就能看到别人的工单,这属于接口越权问题。用随机单号再加一层权限校验,答辩时能加分不少。
3. 把工程跑起来:导入数据库、启动后端、联调小程序
拿到源码压缩包以后,我习惯按“数据库 → 后端 → 前端”的顺序启动,每一步都能独立验证成功再走下一步,最大程度避免“两头都在跑但谁也没通”的茫然状态。
3.1 导入数据库脚本
数据库脚本一般在sql/目录下。用 Navicat 或命令行导入都可以,我常用命令行:
mysql -u root -p -e "CREATE DATABASE IF NOT EXISTS dorm_repair DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;" mysql -u root -p dorm_repair < dorm_repair.sql逻辑说明:第一条命令创建数据库,指定utf8mb4是因为它完整支持中文和特殊符号,避免学生提描述里带个表情符号就报错;第二条命令把整个脚本执行进去,脚本里通常包含建表语句和测试数据。
参数说明:一定注意utf8mb4与utf8的区别——如果原工程是用老版本 MySQL 生成的,可能写成utf8,本地用utf8mb4也兼容,反过来则可能报“字段长度超限”之类的畸形错误。导入时如果报错,往脚本头几行找原因,多半是“数据库已存在”或“表已存在”,删掉重建即可。
3.2 配置 application.yml 并启动后端
导入成功后再看后端配置:
server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/dorm_repair?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver servlet: multipart: max-file-size: 10MB max-request-size: 20MB mybatis-plus: global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 configuration: map-underscore-to-camel-case: true逻辑说明:数据源地址里serverTimezone=Asia/Shanghai必须保留,否则LocalDateTime与数据库交互可能出现时间差 8 小时的问题;MyBatis-Plus 逻辑删除配置能在开发期避免误删数据;驼峰映射开启后,数据库下划线字段自动对应实体类驼峰属性。
启动方式两种:IDEA 里直接运行带@SpringBootApplication注解的主类,或者命令行执行mvn spring-boot:run。看到 “Started” 日志说明后端启动成功,此时浏览器访问http://localhost:8080/api/health这类接口能返回 JSON,就证明 Spring 容器正常起来了。
3.3 小程序端改地址与首屏接口联调
后端起来后,把小程序端request.js的BASE_URL改成http://localhost:8080/api。在微信开发者工具里勾选“不校验合法域名”再编译,首页或者登录页能出现数据,项目就算整体跑通了。此时如果首页白屏,先打开调试器的 Network 面板看有没有请求、后端日志有没有报错——这是最有效的排查路径。
4. 核心流程落地:报修提交、图片上传与非功能设计
工程跑通只是开始。这一章把宿舍报修系统最核心的业务链路逐段实现,你要能对着代码讲清每一段的意义,答辩的底气就在这。
4.1 报修提交:后端接收与参数校验
先写后端接收端:
@RestController @RequestMapping("/api/repair") public class RepairController { @PostMapping("/submit") public R submit(@RequestBody @Valid RepairSubmitDTO dto, @RequestAttribute("userId") Integer userId) { RepairOrder order = new RepairOrder(); BeanUtils.copyProperties(dto, order); order.setStudentId(userId); order.setStatus(0); // 0 表示待受理 order.setOrderNo("R" + System.currentTimeMillis() + (int)(Math.random() * 900 + 100)); order.setCreateTime(LocalDateTime.now()); repairOrderService.save(order); return R.ok(order.getId()); } }逻辑说明:前端传来的 DTO 只包含分类、描述、图片等表单数据,学生身份从 token 中解析出来放进userId,这样能防止用户伪造studentId。orderNo用时间戳加随机数生成,避免连续订单被遍历。status强制设为 0,保证新工单一定从初始状态开始。
参数说明:@Valid触发 DTO 里的校验注解,比如@NotBlank(message = "报修描述不能为空")、@Pattern校验电话格式。校验不通过时会自动抛出异常,配合全局异常处理器返回统一的错误 JSON,避免栈信息直接暴露给前端。
4.2 报修列表与状态流转
@GetMapping("/my") public R myRepairs(@RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer size, @RequestAttribute("userId") Integer userId) { IPage<RepairOrder> pager = new Page<>(page, size); LambdaQueryWrapper<RepairOrder> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(RepairOrder::getStudentId, userId) .orderByDesc(RepairOrder::getCreateTime); repairOrderService.page(pager, wrapper); return R.ok(pager); }逻辑说明:MyBatis-Plus 的Page对象配合page()方法直接返回分页结果,不再手写LIMIT offset, size。用orderByDesc按创建时间倒序,是新工单排在前面,方便学生看最新处理进度。这接口的userId同样来自后端解析的 token,而不是前端传来的参数。
状态流转的核心是后端统一控制:管理员接单时把status改成 1 并分配repairManId;维修工完成时改成 2 并记录finishTime;学生评价后改成 3。不要把这些写在小程序端,否则换一个入口或做后台管理页面时要改两处。
4.3 图片上传前后端完整链路
宿舍报修的痛点产物是“说不清楚坏哪儿了”,所以图片上传是刚需。后端实现:
@PostMapping("/upload") public R upload(@RequestParam("file") MultipartFile file) { if (file.isEmpty()) { throw new BizException("文件不能为空"); } String originalName = file.getOriginalFilename(); String ext = originalName.substring(originalName.lastIndexOf(".")).toLowerCase(); if (!Arrays.asList(".jpg", ".jpeg", ".png").contains(ext)) { throw new BizException("仅支持 jpg、png 格式图片"); } if (file.getSize() > 5 * 1024 * 1024) { throw new BizException("图片大小不能超过 5MB"); } String fileName = UUID.randomUUID().toString().replace("-", "") + ext; Path dest = Paths.get(System.getProperty("user.dir") + "/uploads", fileName); file.transferTo(dest); return R.ok("http://localhost:8080/uploads/" + fileName); }逻辑说明:先做空值、格式、大小三重校验,再生成 UUID 文件名,避免中文文件名或重名导致覆盖。文件写到当前目录下的uploads文件夹,返回完整可访问的 URL。
坑点在于 SpringBoot 默认不认识这个uploads路径,直接返回的 URL 会 404。需要加一个配置类:
@Configuration public class WebConfig implements WebMvcConfigurer { @Value("${file.upload-dir:uploads}") private String uploadDir; @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/uploads/**") .addResourceLocations("file:" + System.getProperty("user.dir") + "/" + uploadDir + "/"); } }逻辑说明:这段代码把/uploads/**的 URL 请求映射到本地磁盘目录,这样图片 URL 才能被浏览器访问。很多人上传成功却显示裂图,就是漏了这一步。
小程序端的传图代码:
function uploadImage(filePath) { return new Promise((resolve, reject) => { wx.uploadFile({ url: BASE_URL + '/repair/upload', filePath: filePath, name: 'file', header: { 'Authorization': wx.getStorageSync('token') || '' }, success(res) { const data = JSON.parse(res.data); if (data.code === 200) resolve(data.data); else reject(data); }, fail: reject }); }); }逻辑说明:wx.uploadFile是微信专门用于文件上传的接口,不能用wx.request代替。name必须与后端@RequestParam("file")的参数名一致,这是前后端约好的“暗号”。
4.4 微信订阅消息与学生通知
维修工接单后如何通知学生?常见做法是微信订阅消息。学生提交报修时弹窗请求授权,授权一次换来一次发送机会。
// 伪代码:发送订阅消息 String accessToken = getAccessToken(); JSONObject body = new JSONObject(); body.put("touser", student.getOpenid()); body.put("template_id", "你的模板ID"); body.put("page", "pages/detail/detail?id=" + orderId); body.put("data", JSON.parse("[{\"key\":\"thing1\",\"value\":\"维修工已接单\"}]")); String result = HttpClientUtil.post( "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=" + accessToken, body.toString());逻辑说明:access_token需要在后端用appid + secret调用微信接口获取,并且必须缓存起来,因为接口每天有调用次数限制。订阅消息的模板 ID 在小程序后台申请,每个模板对应固定的字段类型,thing1这类 key 是模板自带的。
实际做毕设时,这一步可以简化成“状态更新后学生在小程序内刷新看到进度”,毕竟订阅消息还需要申请小程序类目。但如果源码里带了完整的订阅消息实现,答辩时提一句“用微信订阅消息主动触达学生”会很加分。
5. 避坑清单:真机预览、图片 404、时区与登录态的四个高频问题
这个标题的源码我帮人调试过不少次,遇到的问题高度集中在这四个地方。按“现象 → 原因 → 解决”的顺序列出来,每一条都是血泪经验。
5.1 真机预览连不上后端
现象:模拟器一切正常,手机预览时首页转圈几秒后提示“网络异常”。
原因:手机上的localhost指向手机自己,不是开发电脑;而且小程序正式环境要求域名备案并配置 HTTPS,本地开发默认没有这些。
解决:开发阶段把BASE_URL改成电脑的局域网 IP 地址,比如http://192.168.1.100:8080/api,同时要保证手机和电脑连在同一个 WiFi。在“微信开发者工具 → 详情 → 本地设置”里勾选“不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书”,这样真机才允许往非 HTTPS 地址发请求。上线前必须换正式域名并申请 SSL 证书,否则无法过审核。
5.2 图片上传成功但页面显示 404
现象:后端日志显示文件已保存,数据库里也存了/uploads/xxx.jpg,打开图片 URL 却显示 404。
原因:SpringBoot 默认的静态资源路径只有classpath:/static/等,你上传到本地磁盘的uploads目录没有被映射成可访问的 URL。文件虽然写入了硬盘,但 HTTP 找不到它。
解决:在项目里加一个WebMvcConfigurer,用addResourceHandlers把/uploads/**映射到本地路径。具体代码就是 4.3 节给出的那段配置。做完之后重启后端,图片就能正常访问了,这是最容易漏的一步。
5.3 时间查询相差 8 小时
现象:数据库里存的时间是早上 8 点,程序读出来却显示当天零点。
原因:JDBC 连接串没有设置时区,MySQL 驱动默认用服务器本地时区,和 Java 的Asia/Shanghai对不上,导致读写之间差 8 小时。
解决:在数据源连接串末尾加上serverTimezone=Asia/Shanghai,再把 MySQL 的系统时区也设置为东八区。连接串改完重启后端即可。这一步同时也是保证LocalDateTime字段能正常存取的前提。
5.4 微信登录 code 二次使用报错
现象:小程序页面多次点击登录按钮,后端偶尔报invalid code,错误码 40029。
原因:微信的wx.login拿到的code是一次性的,换到openid之后再用同一个 code 去调微信接口就会失效。如果前端每次进页面都重新登录,后端就可能拿着旧 code 去换 openid,自然失败。
解决:小程序端登录成功后把后端返回的 token 存进wx.setStorageSync,后续所有请求只带 token;后端拦截器解析 token,解析成功后直接放行,不再重复调用微信接口。只有 token 过期或不存在时才走登录流程。这样既解决了 code 复用问题,也减少了对微信接口的无效调用。
6. 从能跑到能答辩:自测清单和生产化改造思路
很多人把毕设停在“能跑就行”,但答辩现场最容易暴露问题的恰恰是“没按真实流程走一遍”。我最后给一份带验证点的清单,帮你在上线演示前自查。
先做功能通路验收:用两个不同角色各注册一个账号,学生账号提交一张带图片的报修单,管理员账号看到待受理列表并接单,维修工账号更新状态为已完成,再用学生账号确认并评价。这条主链路能走通,系统最核心的演示就成功了。再做对抗性检查:提交空描述、传一个.exe文件、修改前端请求里的studentId,看后端是否都被拦截——这几点老师在答辩时非常喜欢现场试。
生产化改造层面,最少做三件事。第一件是改多环境配置:
# application-prod.yml server: port: 8080 spring: datasource: url: jdbc:mysql://你的云数据库地址:3306/dorm_repair?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: your_username password: your_password逻辑说明:本地用application-dev.yml,部署到服务器时用--spring.profiles.active=prod切换,避免把本地密码误带到生产环境。第二件是引入 Nginx 做反向代理:Nginx 监听 443 端口,配置 HTTPS 证书后把/api/转发到后端的http://127.0.0.1:8080,同时拦截/uploads/做静态文件缓存,访问速度会明显提升。第三件事是给数据库做定期备份,写个简单的 Shell 脚本每天凌晨用mysqldump导出,压缩后保留最近 7 天。这三项做完,演示时即使老师问到部署运维,也能从容接住。
我自己的习惯是,每次拿到这类项目先在十分钟内跑通,再用半小时走一遍完整报修链路,最后才是看代码细节。这套流程走下来,再冷门的坑也有底气排查。希望帮到你。
本文还有配套的精品资源,点击获取