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

资讯详情

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

公寓报修管理系统开发实战:基于SpringBoot+Vue的工单状态机设计

公寓报修管理系统开发实战:基于SpringBoot+Vue的工单状态机设计 1. 项目核心思路与技术选型拆解1.1 报修管理系统的本质一张工单的生命周期很多人第一次看到“公寓报修管理系统”这个题目第一反应是“这不就是个增删改查吗”但真正动手做过的同学会明白这类系统的核心难点不在CRUD而在业务状态机的设计。一张报修单从被提交到最终关闭中间要经历“待审核→待派工→维修中→待验收→已结束”等多个状态每个状态由谁触发、什么时候允许跳转、跳转时哪些字段要跟着变这才是系统价值的真正体现。我习惯先画一张状态流转表再写代码这里用文字描述你可以照着画在纸上学生报修提交后生成状态为“待审核”的工单管理员审核通过后变为“待派工”不通过则变为“已驳回”并回填驳回原因;管理员指派维修工后进入“维修中”维修工提交处理结果后进入“待验收”;学生验收通过后工单关闭为“已结束”验收不通过则回到“维修中”并附带验收意见。整个闭环中任何一个状态的非法跳转都必须被代码拦截——比如“待派工”状态直接跳到“已结束”这在业务上是不合法的。为什么状态机这么重要因为答辩时评委最常问的问题不是“你这张表有多少字段”而是“如果用户重复提交报修怎么办”“维修工没处理完管理员能不能强制关闭工单”。这些问题的答案本质上都藏在状态流转的边界条件里。建议你在数据库设计阶段就把状态枚举值固定死比如0待审核、1待派工、2维修中、3待验收、4已结束、5已驳回不要用字符串随意表示否则后面写统计SQL、写条件判断的时候会非常痛苦。1.2 为什么是SpringBootVueMySQL这个组合先回答一个所有毕设新手都会问的问题为什么市面上的管理类毕设几乎都是这个技术栈三个原因。第一成本可控。SpringBoot自带内嵌Tomcat装好JDK和Maven就能跑后端Vue用Node装个脚手架就能启动前端MySQL用官方安装包装完改个密码就能连。整个环境搭建加起来半天完成剩下的时间全部留给业务代码。第二学习曲线平滑。SpringMVC的注解式开发、Vue的组件化开发、MyBatis的SQL映射每一块都有海量教程和现成模板遇到问题搜索即有答案。第三符合行业主流。中小型项目里Java后端Vue前端的搭配非常常见练完这个项目你后续去找实习时对主流开发模式不会陌生。这里特别想纠正一个误区不要为了显得高级就随便引入Redis、MQ、微服务。报修管理系统的并发量、数据量远远达不到需要中间件的地步把Redis加进来只是增加了解释成本。答辩时评委问“你的缓存和数据库一致性问题怎么解决”“消息丢失怎么办”新手很难答得圆满。老老实实把单体应用的三层架构做扎实把表设计的约束讲清楚比堆砌一堆用不上的技术更能拿高分。1.3 模块划分先立骨架再填肉拿到题目后别急着敲代码先按角色和业务域把模块切清楚。我的习惯是拆成七个模块登录认证模块处理管理员、维修工、学生三类角色的登录和权限控制报修管理模块学生提交报修、查看进度、验收工单人员管理模块管理员维护学生和维修工的基础信息维修工单模块维修工查看分派给自己的任务、回填处理结果公告通知模块系统级通知和公告的发布统计分析模块按月份、按维修类别统计报修量和完成率个人中心模块修改密码、查看个人报修记录模块拆好之后数据库表的设计就有依据了。报修系统的表一般不会太多核心五张左右就够用户表、报修表、工单表、公告表、评论/评价表。很多人容易加一堆冗余字段比如在报修表里直接把维修工姓名写进去这种做法短期看起来方便后面改维修工信息时就要连带修改历史数据而且违反第三范式。正确的做法是用工单表把报修单和维修工关联起来通过user_id做外键查询。2. 数据库设计与核心表结构解析2.1 建表思路从角色到业务流转数据库是这类系统的地基表结构设计得好后面业务代码写起来顺手设计不好写SQL的时候处处别扭。我建议你在设计时遵循一个原则“用户-角色”做基础报修单做中心工单做关联状态字段留扩展。用户表设计时不要把管理员、维修工、学生拆成三张表。拆成三张表看似清晰实际上登录、权限判断会非常麻烦。更合理的方案是一张user表加一个role字段区分角色如果你觉得一张表字段太多、不够清晰也可以拆成user表和user_info表把角色信息放主表把姓名、联系方式等个人资料放附表。不过考虑到毕设体量一张表加角色字段已经足够还能减少联表查询的复杂度。报修表是整个系统的主表。除了常见的repair_id主键、user_id报修人、description故障描述建议用TEXT类型、location报修位置、images报修图片地址可以用JSON数组存也可以用逗号分隔的字符串、create_time核心字段还有两个status当前状态和priority紧急程度。status字段建议用tinyint类型存枚举值而不是直接存“已审核”“维修中”这样的中文因为中文会占用更多存储空间而且排序、比较都不方便。priority可以用“1普通、2紧急、3特急”报修时如果选择特急列表页要显示红色高亮。维修工单表起到枢纽作用把报修单和维修工关联起来。字段包括work_order_id、repair_id、worker_id、assign_time、finish_time、handle_result、student_evaluation。之所以不直接把维修工ID写在报修表里是因为一张报修单可能被转派给多个维修工第一个维修工没时间处理管理员重新指派给第二个。如果维修工ID在报修表里转派时就要改主表数据而工单表可以保留历史轨迹。2.2 索引、约束与常见截断点验证表建好之后有两个细节经常被忽视。第一个是索引。报修列表页通常会按创建时间倒序排列还要按状态筛选所以create_time和status这两个字段一定记得建索引。联合索引status, create_time的效果比两个单列索引更好因为MySQL走联合索引时能同时命中状态筛选和排序。第二个是外键与级联策略。报修表里的user_id建议设置外键关联用户表但删除策略要小心——用户删除了报修记录不能跟着删否则历史数据就没了。合理的做法是把外键的删除策略设为SET NULL或者干脆不建物理外键只在逻辑层通过字段关联。这是很多毕设项目被扣分的地方“你把用户表删了报修单也一起没了这合理吗”还有一个新手容易踩的坑时间字段的时区问题。MySQL连接URL里最好显式写serverTimezoneAsia/Shanghai否则插入和查询的时间会差8个小时。这个坑很小但排查起来很烦人而且答辩演示时被评委发现时间不对会非常尴尬。2.3 状态枚举与SQL统计的配合状态枚举不仅仅用于界面显示它直接影响统计报表的写法。比如要统计“各月份报修总数”直接对报修表按月GROUP BY即可但要统计“本月维修完成率”就需要在工单表里判断finish_time是否在当月且status处于“已结束”。如果你从一开始就没把状态字段规范化这些统计SQL会写得毫无逻辑。这里分享一个我常用的统计SQL思路月度报修量趋势。SQL大概是这样的SELECT DATE_FORMAT(create_time, %Y-%m) AS month, COUNT(*) AS total FROM repair_record WHERE create_time DATE_SUB(CURDATE(), INTERVAL 12 MONTH) GROUP BY DATE_FORMAT(create_time, %Y-%m) ORDER BY month DESC;类似地完成率的计算思路要先定义清楚“什么是完成”——我建议以“维修工回填结果且学生确认验收”为完成标志而不是维修工填完结果就算完。因为只有学生确认了整个服务闭环才算真正结束。3. 后端核心功能实现与关键代码细节3.1 登录认证与角色权限控制权限控制这部分在答辩里一定被问到所以值得好好讲。最基础的方案是用JWT做登录态管理用户登录成功后后端根据用户ID和角色生成一个Token返回前端把Token存到localStorage后续每次请求都带上Authorization字段。后端用一个拦截器HandlerInterceptor统一校验Token从Token里取出用户ID和角色信息后放入ThreadLocal供当前请求后续使用。很多同学的代码问题出在“权限判断散落各处”。比如报修单的详情接口学生只能看自己的管理员可以全部看怎么实现我的建议是在Controller层做一次统一鉴权不要每个方法各写各的。具体做法是写一个RequireRole注解配合拦截器在方法执行前判断当前用户角色是否满足要求。比如RequireRole({ADMIN, WORKER}) GetMapping(/repair/list/all) public Result? listAll(RequestParam Integer page, RequestParam Integer size) { // 只有管理员和维修工能查所有报修单 }这种方式的好处是权限规则集中定义代码可读性高。但注意别过度设计像“同为学生的两个用户之间能不能看彼此信息”这种细粒度权限在毕设里没必要用RBAC框架去实现——简单地在查询条件里加user_id当前登录用户ID就够了。JWT还有一个细节要提醒Token过期时间别设太长。有些同学为了演示方便把Token有效期设成7天结果就会遇到“管理员把某个用户的账号禁用了但他还能用旧Token访问系统”的问题。解决思路是设置一个合理的过期时间建议4小时同时管理员禁用用户时把该用户的Token版本号1让旧Token失效。这部分的解释在答辩时反而是一个加分项能体现出你对会话安全的理解。3.2 报修单提交的前后端校验链路报修提交功能看似简单实际有两条校验链路必须覆盖。前端的校验主要是表单必填项和格式校验故障描述不能为空报修地址不能为空图片最多上传9张。注意防重复点击——用户连点两次提交按钮会产生两条重复报修单这在演示时很出丑。解决方案是前端提交时用loading状态禁用按钮同时后端再做一层幂等控制比如同一个用户同一地址同一描述5分钟内不能重复提交。后端校验要更狠一点。除了字段非空校验还要检查提交的base64图片大小是否超过限制建议单张不超过5MB检查用户是否被禁用。图片上传的建议做法是先上传到本地目录或OSS数据库只存访问URL不要把图片base64直接存进MySQL不然表会迅速膨胀且查询缓慢。维修工回填处理结果时同样要看状态只有“维修中”的工单才能回填结果而且回填后状态要自动跳到“待验收”前端界面应该在这个动作之后刷新工单列表并给出明确提示。这行逻辑我在很多项目里都见过写漏的漏了之后就会出现“工单状态和操作历史对不上”的数据脏乱问题。3.3 三层架构的代码组织之道后端代码的组织方式直接决定你这套系统好不好维护。我的习惯是标准的controller/service/mapper三层架构再用一个名为common的包放通用类。实体类entity和数据库表字段一一映射DTO数据传输对象只承载接口需要的字段VO则用于给前端返回组装好的数据。有些人图省事直接用Map作为Controller返回值这在外人接手时会非常混乱而且不容易让TypeScript帮你在前端生成类型。Service层要尽量干净只写业务逻辑把事务控制交给SpringBoot的Transactional注解。注意事务的使用场景当一次请求涉及多次写操作时比如新建任务的时候同时派单、记录操作日志必须开启事务否则中途某个写入失败会出现数据不一致。这也是答辩时容易被问到的一个点。MyBatis Plus的使用方面我建议只在单表操作时用它的BaseMapper自带方法多表联查和复杂统计则自己写XML映射。既省去大量样板代码又能在答辩时坦然回答“我了解SQL如何编写”而不是只会调用封装好的方法——这个问题问倒过不少只依赖QueryWrapper的同学。4. 前端Vue实现与前后端联调细节4.1 页面与路由的设计布局前端部分我把整体布局设计成左侧菜单栏右侧内容区用Vue Router管理路由。不同角色的登录用户看到的菜单项各不相同学生看到“我的报修”“提交报修”“公告”“个人中心”;管理员额外多出“报修审核”“维修工管理”“数据统计”;维修工看到的是“待处理任务”“历史工单”。这里要留意路由守卫。路由守卫是前端权限的底线比如一个非管理员用户直接访问/admin路径下的页面会被router.beforeEach拦截并跳转到403页面。它属于体验层面的保护真正的数据安全必须靠后端。我在带学生项目时反复强调前端的路由守卫是“防君子不防小人”你藏了按钮、拦了路由但别人照样能直接调后端API所以后端鉴权绝不允许省略。4.2 用Vuex/Pinia管理全局状态登录用户的信息、角色、Token这些数据几乎每个页面都要用。放进全局状态管理里是最合理的。Vue 2项目用VuexVue 3项目用Pinia。实际项目里我建议把用户信息放Pinia同时持久化到localStorage这样刷新页面后登录态不会丢。需要注意的安全细节是不要把密码存进localStorage存一个脱敏后的用户对象Token即可。SessionStorage也行它的好处是关闭浏览器后自动清空但用户体验上用户关掉浏览器重新打开页面就得重新登录考虑到管理系统要长期挂着使用localStorage更合适。页面间的数据交互要注意参数传递方式。比如报修详情页学生从列表页点击某条记录后进入详情最简单的做法是路由带id参数详情页用id重新请求后端接口。不要直接把整个报修对象通过路由参数传递因为对象序列化进URL会变长甚至报错而且刷新页面后参数会丢失。4.3 利用Element UI快速搭建后台界面前端界面不要从零手写CSS直接使用Element UIVue 2或Element PlusVue 3的组件库。表格用el-table分页用el-pagination表单用el-form弹窗用el-dialog消息提示用el-message。这一套组合下来前端代码量可以压缩到原生写法的三分之一不到而且界面风格统一美观。列表页做成“搜索条件表格分页”三个组件的组合体搜索条件包括状态、日期范围、关键字。这里有个请求参数的细节要注意每次切换查询条件、页码时都要重置页码为1否则你在第3页搜索“卫生间漏水”后端返回的却是从第31条开始的结果对不上搜索条件。这个问题很多同学一开始没注意演示时被导师指出“搜索结果不在第一页”会非常尴尬。4.4 前后端联调与异步请求封装联调阶段最重要的事情是统一axios实例的封装。我在项目里会创建一个request.js文件做三件事设置baseURL添加请求拦截器自动带上Token添加响应拦截器统一处理错误码。比如后端返回401时前端自动跳登录页返回500时弹出统一的错误提示。后端返回结构也建议统一成{code, message, data}的格式前端判断code是否等于200而不是直接拿data去渲染这对后序扩展非常有利。跨域问题在联调时几乎必现。本地开发时前端跑在8080后端跑在8081浏览器会发起跨域请求。解决办法有两种前端用Vite或Webpack的代理转发或者后端加CORS跨域配置。我更推荐第一种因为生产环境中前后端本来就是同源部署前端代码在开发阶段通过代理把/api路径下的请求转发到后端这样生产环境就不需要额外的跨域处理了。如果你用第二种方式在后端全局放开CORS演示阶段看起来一切正常但部署后如果没配置好反而多一个排查点。5. 项目打包、部署与文档交付全流程5.1 前端打包与后端jar包生成毕业设计交付的时候评委很可能会要求你现场展示一个能跑起来的系统所以打包部署这一步千万别等到最后一天才做。前端部分先在项目根目录执行npm run buildVue会自动生成一个dist目录里面是编译后的静态文件。这里要注意dist目录里的文件不要直接双击打开因为路由用的是history模式直接打开会404必须放到Nginx或用Nginx托管。后端部分比较简单在pom.xml里确认打包方式是jar包然后在IDEA里执行mvn clean package即可。如果打包时报内存不足可以调整Maven的MAVEN_OPTS。拿到的jar包直接用java -jar xxx.jar即可启动。建议在application.yml里把数据库连接、端口等配置外置到环境变量方便部署时按实际情况修改不用重新打包。这里提醒一个非常常见的部署坑端口占用。后端启动时如果提示“Port already in use”在Linux下执行netstat -tlnp查一下被谁占用然后杀掉相应进程或改配置端口。Windows下则用netstat -ano定位PID再在任务管理器里结束。这个排查方法在答辩现场路过一次就长记性了。5.2 Nginx反向代理与前后端同源部署生产环境推荐前后端“同源部署”即都放到一台服务器的80端口下通过不同路径区分。在Nginx的server块里/根路径指向前端dist目录/api路径反向代理到后端jar包监听的8081端口server { listen 80; server_name your-domain.com; location / { root /opt/apartment-repair/frontend/dist; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8081; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }try_files这行是路由history模式的关键——否则刷新前端页面时会找不到对应路径。这一点你在本地可能不容易察觉但部署后刷新某个子页面时如果404八成就是try_files没配置。Nginx启动后要注意别用默认的80端口和已有网站冲突。如果同一台服务器已经运行了别的Web服务就把server_name写清楚或者换用其他端口。SAN证书的HTTPS配置不强制但如果你有域名加一张SSL证书会显得项目更专业这部分文档网上很多照着做即可。5.3 论文与部署文档的撰写策略很多同学代码写完了论文拖到最后一周才动手结果写得极其痛苦。我建议的流程是先搭论文框架再边写代码边补充内容。因为论文里的很多截图和操作截图都是开发过程中顺手留下来的等全部开发完再回头补截图界面数据已经是改过的版本了前后对不上答辩时被质疑会很难受。论文的结构我建议这样安排第一章绪论背景、意义、国内外现状、第二章相关技术介绍、第三章系统需求分析功能性需求非功能性需求、第四章系统设计架构设计数据库设计、第五章系统实现每个模块的文字描述截图核心代码片段、第六章系统测试功能测试用例表结果分析、第七章总结与展望。注意核心代码要放但不要整段堆上去每段代码配两三句说明讲清楚这段代码解决什么问题、用了什么关键API。部署文档写得越详细越省事。把从装JDK、装MySQL、初始化数据库、打包、部署Nginx、启动jar包到最终访问系统验证功能的每一步写清楚。我建议带目录、带截图、带每一步的验证方法。这份文档不光是交付物更是你自己答辩前复习的“逃生手册”——万一现场环境有问题照着文档快速排查比临时想解决方案靠谱得多。6. 常见问题与排错经验实录6.1 数据库相关高频问题这类问题我在指导毕设时见得太多了。第一个是MySQL 8.0的密码认证方式。MySQL 8默认用caching_sha2_password而某些老版本的JDBC驱动不认识它连接时报Public Key Retrieval is not allowed。解决方法是在JDBC连接URL后加allowPublicKeyRetrievaltrue或者把驱动升级到8.0对应的版本。如果你装的是5.7则要注意字符集乱码问题连接URL里加useUnicodetruecharacterEncodingutf8。第二个是初始化SQL执行报错。很多交付的源码都带一个.sql文件要求在本地导入。导入时数据库的字符集如果和SQL文件里不一致中文数据会变成乱码。建议统一用UTF-8导入前先执行SET NAMES utf8mb4。第三位是端口冲突MySQL默认3306端口如果被占用会导致服务无法启动排查时可以用命令查询端口占用后改掉MySQL配置或者杀掉占用进程。6.2 前端与后端联调排错开发过程中最常见的场景是前端页面上报了个400或500错误后端控制台却没看到明显异常。这时候第一件事不是看代码而是打开浏览器的开发者工具Network面板看具体请求/响应内容和状态码。如果是400检查参数名是不是对不上SpringBoot接收前端字段时如果用了RequestBody那么前端请求头Content-Type必须是application/json且JSON字段名要和后端实体/Map的key一致。我见过太多因为前端多传了一个无关字段导致后端序列化报错的例子。如果是跨域报错页面会提示CORS policy相关字样。开发环境强烈建议用Vite代理具体在vite.config.js里配置server: { proxy: { /api: { target: http://localhost:8081, changeOrigin: true } } }后端如果开启了CORS要在SecurityConfig如果有Spring Security里放行相关路径。这个微小的配置会让你的联调体验显著提升。另一个高频错误是后端返回数据中时间字段格式不对。SpringBoot默认返回的时间是UTC格式前端显示出来是“2025-06-01T07:30:00.00000:00”这样非常戳眼睛。解决思路是配置JSON序列化的日期格式比如在application.yml里设置spring.jackson.date-formatyyyy-MM-dd HH:mm:ss时区设为GMT8。这样前端拿到的就是友好的字符串不用再做二次处理。6.3 打好“答辩安全牌”的实操建议最后给你一个很有用的检查清单答辩前一天逐项过一遍管理员账号、维修工账号、学生账号三个角色的演示账号是否都已经准备好能在答辩现场30秒内登录进去数据库服务是否已设置为开机自启避免现场重启后访问数据库失败前端打包后的dist目录和时间戳、后端jar包是否是最新版本避免“代码是新的跑的是旧的”备用方案准备一台本地虚拟机或者备用服务器当主演示环境出现问题时5分钟内能切到备用环境演示数据要“带戏”提前造几组有图片、有完整状态流转的历史报修单方便演示时直接展示不同状态的界面答辩现场最忌讳的就是“卡在一个报错上5分钟”。哪怕你平时很熟练也建议提前走一遍从启动到演示的完整流程把每一步可能出问题的地方提前修好。我见过有学生演示时报修图片临时加载不出来就是因为图片存在本地后端重启后目录路径变了存储目录没做统一配置。这种小问题完全可以在部署文档里预先给出一段“启动前检查”列表现场按单排查效率最高。这个项目后续还能怎么扩展如果你学有余力可以往两个方向走一是接入微信小程序把学生报修端放到小程序里方便移动端直接拍照上传;二是给数据统计页面加图表展示用ECharts做月度趋势折线图和维修类型饼图。这些扩展不会改变系统核心架构但对找工作时展示你的前端数据可视化能力很有帮助。回到最初说的那句话——这类系统的灵魂是工单状态流转把生命周期管清楚了后端的逻辑、前端的交互、数据库的设计就都顺理成章了。
返回列表