
校园里报修这件事听起来不大做起来却一直让人头大。今天教室的多媒体设备坏了明天宿舍空调漏水后天实验室仪器罢工传统靠电话、微信群、纸质单据的报修流程要么信息对不上要么进度全靠催。用SpringBootVue做一套前后端分离的校园设备维护报修系统正好把这个场景系统化用户在线提交工单、维修工接单处理、管理员统一调度全程可追踪。这篇博文把这套系统的需求分析、数据库设计、核心模块实现到部署上线的完整链路都过一遍适合正在做毕业设计的学生、准备接手类似项目的开发新手以及想了解前后端分离项目从零到一真实落地过程的朋友参考。1. 项目概述与需求拆解1.1 高校报修场景的真实痛点在做这套系统之前我特意跟学校后勤的老师聊过一圈又蹲在几个宿舍楼的报修群里观察了一阵子。不是说传统方式完全不行而是它的效率瓶颈非常明显。几个典型的痛点第一报修入口分散。有人打电话有人在微信群喊一嗓子有人跑到后勤办公室填单子信息源五花八门后勤处要花大量时间统一汇总。第二工单流转没有留痕。一个报修单报上去之后谁接了、修到哪一步、为什么还没处理用户完全不知道只能反复催问后勤老师也被问得焦头烂额。第三维修工作量无法量化。月底做统计的时候只能靠翻聊天记录和纸质单子人工数效率低还容易漏。这些痛点直接决定了系统的核心逻辑一个统一的报修入口、一个完整的工单生命周期、一套可量化的数据统计能力。这里面最关键的倒不是功能炫技而是把报修→派单→维修→验收→评价这条主链路走通所有角色围绕同一份数据协作谁在什么时间做了什么操作后台全部有记录。1.2 角色梳理与业务流程系统涉及的角色不复杂但每个角色的权限边界必须清晰。我梳理下来是三类普通用户、维修工、管理员。如果学校规模大还可以再细分校区管理员和维修班长但核心权限模型保持不变。普通用户负责提交报修申请填写设备类型、故障描述、联系方式和期望维修时间提交后可以查看工单进度维修完成后进行验收和评价。维修工看到属于自己的待办工单接单后更新维修状态填写处理结果和耗材使用情况。管理员是系统的核心角色负责审核新提交的工单、分配维修工、处理超时工单、查看各类统计报表并对用户和维修工账号进行管理。业务流程上我建议把状态机先定义清楚后面开发会顺畅很多。一条典型的主链路是用户提交报修→管理员审核→管理员派单→维修工接单→维修完成→用户验收→双方评价。过程中可以插入取消、驳回、转派等异常分支。这里要特别提醒状态流转一定要在后端做合法性校验不能只在前端控制按钮显隐否则接口被直接调用就能跳过流程这在答辩演示时尤其容易暴露问题。1.3 功能模块全景系统的功能模块划分按照角色来切割最清楚。用户端主要包含报修申请、工单列表、进度详情、消息通知、历史记录、个人中心。维修工端包含待接单列表、工单处理、维修记录、我的评价、工作量统计。管理端包含用户管理、工单审核、派单调度、设备类型管理、故障分类管理、数据统计看板、系统公告。如果项目定位是毕业设计功能不需要追求大而全但一定要有亮点。我当时在管理端加了一个维修效率分析模块用ECharts展示各类设备的平均维修时长、维修工接单量排行、故障类型分布这套可视化看板在演示时非常加分因为评委能直观看到整个业务流程跑起来之后的真实数据沉淀。另外消息通知建议用站内信实现不要一上来就接WebSocket或者短信服务复杂度和成本都不划算站内信的轮询策略完全够用。2. 技术选型与架构思路2.1 为什么是SpringBootVue这个组合在校园项目里几乎是标准答案不是因为它最先进而是因为它足够成熟、生态完善、学习成本可控而且企业应用里用得确实多。SpringBoot侧核心价值在于自动配置和起步依赖。以前用SSH或者SSM配置文件动辄几百行光搭环境就劝退一拨人。SpringBoot通过大量的AutoConfiguration把数据源配置、MyBatis整合、事务管理、参数校验这些东西都做了合理默认开发者只需要关注业务代码。它内置的Tomcat容器也省去了单独部署Web服务器的麻烦打包成可执行的jar直接跑起来。Vue侧的优势是组件化开发和响应式数据绑定。报修工单这种频繁增删改查的页面用Vue的双向绑定做表单交互非常顺手数据变化自动驱动视图更新不用像JQuery时代那样手动操作DOM。Vue Router做单页应用的路由切换Vuex或者Pinia管理全局状态比如当前登录用户信息和未读消息数量。前后端分离的架构还有一个隐藏优势前后端可以并行开发只要提前定义好接口文档。学生做毕业设计通常是单兵作战但这个架构习惯对以后进入企业做协作开发非常有帮助而且简历上写前后端分离项目比写单体JSP项目要有说服力得多。2.2 前后端分离下的工程结构工程结构建议分成三个独立部分后端SpringBoot工程、前端Vue工程、数据库脚本。不要混在一个目录里打包部署也要分开。后端工程建议按经典的分层结构组织Controller层负责接口定义和参数接收Service层负责业务逻辑Mapper层负责数据库操作。这里有个细节一定要建DTO数据传输对象和VO视图对象不要直接把数据库实体类返回给前端。原因很简单用户表里有密码字段如果直接把User实体序列化返回密码就跟着响应体暴露了这是安全底线问题。另外DTO还能帮你隔离数据库表结构和前端展示层的变化以后加字段或者改表结构不会牵一发动全身。前端工程我用Vue CLI创建的默认结构src目录下分views、components、router、store、api、utils几个目录。api目录里按后端接口模块拆文件比如user.js、order.js、statistics.js封装axios请求。这样做的直接好处是页面组件里不会散落一堆请求代码后续维护接口地址变更只需要改一个文件。2.3 接口风格与Token鉴权接口设计上我选择了RESTful风格但也没有过度追求所谓的纯REST而是以实用为主。报修工单的接口大致是这样设计的GET /api/order/page 分页查询工单POST /api/order 提交报修工单PUT /api/order/{id}/assign 管理员派单PUT /api/order/{id}/status 更新工单状态PUT /api/order/{id}/evaluate 用户评价统一返回结构是必做的。我定义的响应体格式是{ code, message, data }code为200表示成功其他为业务异常码。前端axios拦截器统一处理非200时弹提示401时跳登录页这样每个接口不需要重复写异常处理逻辑。鉴权方案用的JWT。登录成功后后端生成Token返回给前端前端存在localStorage里每次请求在header带上Authorization字段。后端做一个拦截器解析Token把用户信息放到请求上下文里。这里想强调一个容易踩的坑JWT是无状态的服务端无法主动让Token失效所以学生用户改了密码之后旧Token在一段时间内仍然有效。如果项目要求严格一点可以引入Redis存储Token黑名单但毕业设计阶段不用过度设计设置一个合理的过期时间比如2小时就够了。3. 核心模块实现与数据库设计3.1 数据库表结构规划数据库是这类业务系统的地基表结构合理性直接决定后续开发是顺滑还是处处磕绊。我按模块拆成四组用户权限组、报修业务组、设备资产组、通知评价组。用户权限组核心是user表字段包括id、username、password、real_name、phone、role、campus、create_time。role字段用字符串存储取值为ADMIN/REPAIRER/USER。没有单独设计角色权限表因为这个系统的角色是固定的用字段区分比多表关联更简单直接。报修业务组最核心的是repair_order表字段包括id、order_no工单编号、user_id报修人、device_name设备名称、device_location设备位置、fault_description故障描述、fault_type故障类型、status工单状态、assignee_id维修工、report_time、accept_time、finish_time。这里order_no建议用时间戳加随机数生成保证唯一性且具有可读性比如20250607103025。设备资产组可以做一张device表记录学校主要设备的基本信息和所属实验室或教室。这张表的定位是辅助性的报修的时候用户可以从已有设备列表中选择也可以手动填写避免强制绑定导致用户嫌麻烦。3.2 用户登录与密码安全登录接口的逻辑其实很常规前端提交用户名和密码后端查出用户比对密码一致则生成Token返回。但密码存储方式必须用BCrypt加密绝对不可以明文存储或者只做MD5。为什么MD5虽然不可逆但彩虹表攻击非常成熟网上随便一搜就有海量明文和密文的对照库等于没加密。BCrypt是加盐哈希每次加密结果都不同暴力破解的成本极高。SpringSecurity和Sa-Token这两个框架我都试过毕业设计初期如果精力有限用Sa-Token会轻松一些它的API设计对新手友好登录、权限校验、踢人下线都封装好了。SpringSecurity功能更强大但配置复杂度高新手容易在过滤器链上卡几天。我最后使用的是SpringSecurity加自定义拦截器方案其实主要就是配置了密码加密器和JWT过滤器其他模块用不到的Security功能就让它走默认。这里要提醒一个细节JWT过滤器的放行规则一定要提前规划好。登录接口、注册接口、前端静态资源可以匿名访问其他接口都要校验Token。我当时在配置放行路径时少加了一个静态路径结果前端验证码图片一直加载不出来排查了一下午才发现是Security拦截了图片请求。3.3 报修工单状态机设计工单是整个系统的灵魂状态机的设计直接决定业务是否闭环。我最终定义了6个状态用status字段的整数来标识开发的时候不容易搞混0待审核用户刚提交管理员还没处理1待派单管理员审核通过但还没指定维修工2待处理已派给维修工等待接单3维修中维修工已接单正在处理4待验收维修工标记完成等待用户确认5已完成用户验收通过并评价状态迁移必须是单向的。比如从3维修中只能走到4待验收不能跳回到1待派单。我在Service层写了一个状态校验工具任何流转操作都先校验当前状态和目标状态是否在合法的迁移路径上不合法直接抛异常。这个设计在答辩演示的时候非常好讲因为评委问如果维修工把单子误操作了怎么办你就可以清晰地回答状态机把住了这个边界。3.4 派单策略与评价体系派单是管理端的核心操作。最简单的方案是管理员手动选择维修工但这种方案对维修工数量多、工单量大的场景来说效率低。我的方案是手动派单自动推荐结合管理员选择工单后系统按技能匹配度、当前待处理单量、历史接单量三个维度给维修工打一个推荐分按分数排序展示。管理员可以一键采纳推荐或者手动改派。推荐分的计算逻辑不复杂skillMatch字段由故障类型和设备类型匹配表关联得出匹配为1不匹配为0pendingCount是维修工当前未完成工单数越少越好historyCount是历史完成单量越多越好。最后计算score skillMatch * 0.5 (1 - pendingCount / maxPending) * 0.3 min(historyCount / 100, 1) * 0.2。这个公式是我自己调参调的不一定最优但演示效果很好因为能明显看出推荐结果有业务逻辑在里面。评价模块分为用户对维修工的评价包括评分和文字评价。评分采用1到5星这个数据可以直接做聚合分析比如按维修工维度算平均分按维修类型维度算平均分喂给管理端的数据看板。4. 实操过程与核心环节实现4.1 开发环境与版本规划版本规划是很多新手容易忽略、但非常影响开发体验的一环。我强烈建议在动手之前就把SpringBoot、JDK、Maven、Node.js的版本锁定好并记录下来。原因很简单SpringBoot版本和JDK版本强相关2.x系列基于javax包3.x系列迁移到了jakarta包如果你从网上找到一个SpringBoot 2.7的教程自己却装了SpringBoot 3.2的环境很多代码会编译报错。我实际使用的版本组合供参考JDK 1.8、SpringBoot 2.7.18、MyBatis Plus 3.5.3、Maven 3.8.8、Node.js 16.20.2、Vue 2.6.14、Element UI 2.15.14。这套组合相对经典网上资料丰富遇到问题容易搜到解决方案。不要一开始就追新版本稳定大于一切。项目初始化我用的是Spring Initializr生成后端基础工程版本选2.7.18依赖勾选Spring Web、MyBatis Plus的Druid数据源、Lombok、Validation。其实Spring Initializr的依赖列表里没有MyBatis Plus需要生成后在pom.xml手动加依赖。前端用Vue CLI创建项目选择Vue 2版本装Element UI和Axios就够用了。4.2 报修提交与文件上传的处理报修提交是整个系统使用频率最高的操作前端交互上要尽量降低用户填写成本。我的处理是用户输入设备名称时做了远程搜索联想调后端接口按关键字模糊匹配已有设备列表用户选中的设备自动带出位置信息。如果用户搜索不到也可以手动输入后台会记录为未收录设备。故障描述这里要处理一个安全问题XSS攻击。用户填写的文本里有可能会恶意写入script标签如果后端不做处理直接存库再原样渲染到管理端管理员打开工单详情就可能执行了恶意脚本。我在后端对故障描述字段做了HTML标签过滤只保留纯文本前端在Vue模板里也默认启用了{{ }}插值的转义双重保险。关于上传故障图片这个功能我斟酌了一下是否要做。做的话需要处理文件上传、访问路径映射、文件大小限制、存储目录规划等工作量不小。从实际场景来看图片能帮助维修工提前判断故障情况确实有业务价值所以最终做了。文件存储放在服务器本地目录数据库存访问URL。上传接口限制了单张不超过5MB支持jpg、png格式。这里有个坑本地存储的文件在项目重启后路径可能变化如果打包部署到服务器要记得把上传目录配置为绝对路径不要用相对路径。4.3 管理端看板的实现管理端的数据看板是我花心思最多的模块也是整个项目最有数据价值的地方。统计维度我做了四个报修趋势折线图按天展示近30天的新增报修单量和完成单量看整体负载情况。实现方式是在后端写一个聚合查询按日期分组统计状态字段。MyBatis Plus里可以用QueryWrapper加select count和date_format函数一条SQL就能搞定。故障类型饼图展示各类故障的占比分布帮助后勤了解设备的主要问题集中点。比如某段时间投影仪故障占比突然升高可能说明有一批设备到年限了该考虑批量更换。维修工工作量柱状图按维修工维度展示完成工单量和平均维修时长直接体现工作负荷。工单状态分布用数字卡片展示待审核、待派单、维修中、已完成的数量让管理员一打开后台就知道当前有哪些积压。前端的可视化是用ECharts做的和后端接口对接。ECharts的图例、提示框、自适应都封装得很好但有一个细节图表必须在Vue的nextTick之后初始化否则容器还没渲染完成图表宽度会是0。另外路由切换后离开页面要销毁图表实例不然会内存泄漏多切换几次页面浏览器就会卡顿。4.4 后端打包与前端构建打包部署是让项目真正跑起来的临门一脚。后端在项目根目录执行mvn clean package -DskipTests会在target目录下生成可执行的jar包。注意要跳过测试因为如果某个单元测试写了外部依赖打包时可能因为环境不一致而失败反而耽误时间。对于SpringBoot 2.7项目打包出来的jar默认是Spring Boot Fat Jar内部包含了Tomcat和所有依赖直接java -jar xxx.jar就能启动。启动时可以通过命令行参数指定配置文件比如java -jar xxx.jar --spring.profiles.activeprod这样可以区分开发环境和生产环境的数据库配置。前端构建命令是npm run build生成dist目录。构建的时候有几个注意点一是vue.config.js里的publicPath要设置为相对路径./否则部署到服务器子目录时资源路径会404二是接口地址要改为服务器的实际地址不要用localhost前端很多部署问题都是接口地址写错导致的。4.5 Nginx反向代理配置前端打包出来的dist目录是纯静态文件需要挂在Nginx下。Nginx在这里承担两个职责一是托管前端静态页面二是反向代理后端API请求。一个典型的配置示例是server { listen 80; server_name your-server-ip; # 前端静态资源 root /usr/share/nginx/html; index index.html; # 解决前端路由history模式的404问题 location / { try_files $uri $uri/ /index.html; } # 后端API反向代理 location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }这个配置里的try_files $uri $uri/ /index.html;是关键。Vue Router如果启用了history模式浏览器直接访问/order/detail/123这样的路径时Nginx找不到对应的静态文件会返回404加上try_files后所有路径都会回退到index.html由前端路由接管。这个坑非常典型我排查了很久才明白。如果服务器上还跑了其他服务注意把后端端口改成8080别和Nginx的80端口冲突。上传的图片文件建议单独配置一个静态映射比如location /uploads/ { alias /data/uploads/; }。5. 常见问题与排查技巧实录5.1 版本过高导致的启动失败这个题目对应的热搜词里有个很有代表性的问题springboot版本太高。如果你创建项目时直接选了SpringBoot 3.x又在网上复制了一段2.x的代码极大概率会报ClassNotFoundException: javax.servlet.Filter之类的错误。原因我之前提过了Java EE迁移到Jakarta EE3.x版本把包名从javax改成了jakarta。这不是改一个import就能解决的很多第三方库也要用适配了jakarta的版本。建议的处理方案是要么老老实实回到2.7.x要么整体切换规范。对于校园系统这种业务场景2.7.x已经非常成熟没有必要追求最新版。这也是我为什么在部署部分反复强调版本规划的原因。5.2 MyBatis Plus的坑与配置MyBatis Plus确实让CRUD开发效率提升了一个量级内置的BaseMapper提供了单表的增删改查方法不需要写任何XML。但是有几个点容易踩坑。第一个是逻辑删除。我在表设计时加了deleted字段配置了TableLogic注解这样调用deleteById时实际执行的是update语句把deleted置为1。这个机制在单表查询时很好用但如果你手动写了联表查询SQLMyBatis Plus不会自动拼接逻辑删除条件必须自己在SQL里加。我当时漏了手写SQL里的条件导致已经逻辑删除的数据在报表里还能查询出来调试了半个多小时。第二个是自动填充。createTime和updateTime这类字段如果每张表都手动set非常容易漏。MyBatis Plus提供了MetaObjectHandler接口实现insertFill和updateFill方法配合实体上的TableField(fill FieldFill.INSERT)注解可以在插入和更新时自动填充时间字段。这个配置花10分钟做完能省后面几百次的重复赋值。5.3 前端跨域问题的三种解法跨域这个问题是前后端分离项目的必经之路。开发环境下前端跑在8080端口后端跑在9090端口浏览器会直接拦截不同源的AJAX请求。我试过三种解决方案各有适用场景。第一种是后端CORS配置。在SpringBoot的配置类里写一个WebMvcConfigurer实现类重写addCorsMappings方法允许指定来源跨域。这种方式配一次全接口生效开发环境最方便但生产环境不建议放开所有来源的跨域有安全风险。第二种是前端代理。在vue.config.js里配置devServer.proxy把/api开头的请求转发到后端地址。这种方式的好处是浏览器始终认为请求是发给前端的不会产生跨域。而且开发环境和生产环境互不影响生产环境用Nginx代理开发环境用webpack代理。第三种是Nginx反向代理部署时用上文已经写过配置了。我的建议是开发环境用第二种生产环境用第三种不要用后端CORS方案直接上生产。5.4 单元测试的实践建议很多学生项目不写测试但在这个项目的实操过程中我的体会是核心的工单状态流转和派单推荐算法一定要写单元测试。因为这些逻辑一旦出错影响的是整条业务链而且人工验证很难覆盖所有分支。我写的测试主要集中在Service层用JUnit 5加Mockito。测试用例重点覆盖状态迁移的合法和非法路径、派单推荐分排序逻辑、工单超时判断逻辑。由于数据库连接在测试环境可能不稳定我给Service层注入的是Mock的Mapper这样测试跑得快且不依赖外部环境。一个建议测试方法命名要像描述业务场景一样比如should_assign_order_to_repairer_when_status_is_pending_assign。这样测试失败的时候从名称就能直观看出是哪个业务场景挂了排查成本会低很多。另外SpringBoot 2.7集成测试建议在测试类上加上Transactional注解让每个测试方法执行完自动回滚数据避免测试数据污染数据库。5.5 部署后的管理维护项目部署上线之后还有几个日常维护的点要提前考虑。日志是排查线上问题最重要的抓手。SpringBoot默认的日志输出到控制台一重启就丢了所以一定要配置日志文件输出。在application.yml里配置logging.file.name指向一个绝对路径比如/var/log/campus-repair/app.log同时可以按日志级别拆分文件错误日志单独记录。数据库的定时备份也不能省。可以写一个简单的shell脚本用mysqldump导出数据库配合crontab每天凌晨执行。虽然校园系统数据量不会很大但养成备份习惯是专业性的体现。这个细节在企业里是运维的基本要求写进项目文档里非常加分。写在最后这个项目从需求调研到最终部署我大概花了三周左右的时间。回过头来看最有价值的不是代码本身而是把一条完整的业务链路——从用户在前端点下提交报修按钮到维修工收到单子、处理完、用户验收评价——真正跑通的成就感。整个过程中踩过的坑、试过的错最后都沉淀成了这篇博文里的经验。如果你正在做类似的系统我个人的经验是先花两天把需求和数据库表设计吃透再动手写代码效率远高于边写边改。表结构定错了后面改起来极其痛苦这个亏我替你先吃了。项目做完之后把部署文档和讲解PPT整理好重要性不亚于代码本身——答辩或者交接的时候一套清晰的文档能让你的成果被快速理解。祝开发顺利有问题欢迎交流。