1. 项目概述与核心思路拆解
1.1 这个系统到底是做什么的
先说结论:这是一个面向粮库日常管理场景的后台管理系统,核心解决的是三件事——设备巡检、维修派工、故障报修。我接触过不少仓储类的毕设项目,说实话,粮库设备管理这个选题在Java毕设里属于“看起来普通、实际很能打”的类型,因为它的业务链路非常完整,从设备台账到巡检计划,从故障上报到维修工单闭环,整条线走下来,涉及的技术点覆盖了Spring Boot后端开发的绝大部分高频考点。
我用大白话给你翻译一下这个系统的使用场景。粮库里有很多设备,比如通风设备、粮情检测设备、输送设备、除尘设备等等。以前的管理方式可能是纸质登记或者Excel表格,设备坏了靠人传话,修没修好也无从追踪。这个系统要做的事情就是把这一套流程搬到线上:负责人可以提前制定巡检计划,巡检员在系统里填写巡检记录;设备出现故障时,仓管员可以发起报修申请,维修主管审核后派发维修工单,维修工完成维修后再回填维修结果。整个过程都有记录、有状态、有流转,后期统计查询也方便。
1.2 为什么这个选题值得做
我每年都会看大量毕设项目的选题,如果让我排序推荐,这类“设备管理+工单流转”的项目属于性价比很高的一类。原因有三点。
第一,技术栈主流。Spring Boot + MyBatis Plus + MySQL这套组合是目前Java后端岗位最普遍的技能要求,做这个项目相当于把工作中最常用的技术栈完整走了一遍。你如果简历上写“熟悉Spring Boot”,但拿不出一个完整的业务项目支撑,面试官一问就露馅。这个项目做完后,你对控制层写接口、服务层做业务、持久层操作数据库这套标准分层会形成肌肉记忆。
第二,业务逻辑有深度。它不是那种简单的增删改查CRUD系统。巡检、维修、报修这三个模块之间存在状态联动,比如设备报修后,不能再创建巡检计划;维修工单处于“待验收”状态时,维修员不能重复提交。这种业务约束条件的处理和实现,是面试官非常爱问的“你项目里有什么难点”的标准答案来源。
第三,扩展空间大。你可以在基础版本上叠加很多高级功能。比如用WebSocket实现维修进度实时通知,用ECharts做设备故障率统计图表,用AOP切面记录操作日志,哪怕只是做一个简单的Excel导出功能,都能让项目的完整度提升一个档次。
1.3 整体技术架构选型与理由
我用表格把技术选型列出来,然后逐个解释为什么这么选。
| 技术 | 选型 | 说明 |
|---|---|---|
| 后端框架 | Spring Boot 2.7.x | 稳定版本,适配MyBatis Plus和JDK 8,避免过高版本带来的兼容性坑 |
| ORM框架 | MyBatis Plus | 简化单表CRUD,分页插件好用,适合毕业设计快速开发 |
| 数据库 | MySQL 5.7 / 8.0 | 开源稳定,支持事务和复杂关联查询 |
| 前端 | Thymeleaf / Vue 3 + Element Plus | 取决于你是否想挑战前后端分离,后面细说 |
| 权限认证 | Spring Security + JWT | 实现用户登录、角色权限控制 |
| 工具库 | Hutool、Lombok、EasyExcel | 减少重复代码,提升开发效率 |
关于Spring Boot版本,我特别提醒一句。现在网上很多教学视频用的是Spring Boot 2.3甚至更老的版本,而Spring Boot 3.x发布后,很多初学者喜欢盲目追求新版本,结果在整合Spring Security、MyBatis Plus时遇到一堆兼容性问题。我做这个项目时用的Spring Boot 2.7.18 + JDK 8这一套组合,原因非常简单:生态最成熟。无论是百度搜问题还是查官方文档,能直接抄作业的案例最多,很多东西导入即用,不需要改配置。你能把项目顺顺利利跑起来、把流程弄懂,比用了最新版本但卡在环境配置上,价值要大得多。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心业务模块设计与数据库建模
2.1 三大核心业务链路分析
在设计数据库之前,一定要先把业务链路图画清楚,否则建表的时候就会很混乱,一会儿想加字段一会儿又想改结构。我把系统里的三条核心链路拆开来看。
第一条链路:设备巡检。 巡检员发起巡检任务(或者由管理员按计划创建)→ 填写巡检记录 → 记录每台设备的运行状态、温度、湿度、异常描述等信息 → 如果发现异常,可以在巡检记录里直接关联生成一条报修申请。这个链路强调的是“计划-执行-反馈”,所以需要有两张核心表:巡检计划表和巡检记录表。
第二条链路:报修申请。 任何人(主要是仓管员或巡检员)发现设备故障 → 填写报修单(描述故障现象、紧急程度)→ 维修主管审核 → 审核通过后生成维修工单。这里要注意的是报修单和维修工单是一对一的关系,报修人是“报”的角色,维修人是“修”的角色,两个角色之间通过工单状态产生关联。
第三条链路:维修工单闭环。 维修主管派工 → 维修工认领工单 → 填写维修过程和更换配件 → 提交验收 → 负责人验收通过 → 工单归档。维修工单的状态是整个系统里最复杂的部分,我建议状态流转字段设计为数字字典,而不是直接存中文状态,这样可以避免前端显示和后端逻辑耦合。
2.2 数据库表结构设计要点
数据库表设计的原则是“够用、不冗余、方便扩展”。我这个系统的核心表大概有9张左右,我挑几张关键的表给你看设计思路。
用户表(sys_user)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| username | varchar(50) | 登录名,唯一 |
| password | varchar(255) | BCrypt加密后的密码 |
| real_name | varchar(50) | 真实姓名 |
| role_id | int | 角色ID,关联角色表 |
| dept_id | bigint | 所属部门(粮库库点) |
| phone | varchar(20) | 联系电话 |
| status | tinyint | 账号状态,1正常 0禁用 |
注意,用户表里我建议直接设计一个role_id字段关联角色表,不搞复杂的RBAC多对多权限模型。毕设里用“角色-功能”的简单映射就够了,如果角色表、菜单表、用户角色表、角色菜单表四张表全设计出来,工作量翻倍不说,答辩时你也不一定能讲清楚。这里的取舍要心里有数。
设备表(device_info)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| device_code | varchar(50) | 设备编码,唯一 |
| device_name | varchar(100) | 设备名称 |
| device_type | varchar(50) | 设备类型(通风、粮情、输送等) |
| location | varchar(100) | 安装位置 |
| install_date | date | 安装日期 |
| status | tinyint | 1正常 2维修中 3报废 |
| remark | varchar(500) | 备注 |
这里要重点考虑设备状态与工单状态的联动。比如设备状态为“维修中”时,前端页面应该禁止再次发起报修,这个逻辑可以在后端服务层做校验,而不是单纯靠前端disabled。我在实现时写了一个公共校验方法,专门处理“当前设备是否处于可操作状态”,被报修和派工两个接口复用,代码复用性很高。
巡检记录表(inspection_record)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| plan_id | bigint | 关联巡检计划 |
| device_id | bigint | 被巡检设备 |
| inspector_id | bigint | 巡检人 |
| inspect_time | datetime | 巡检时间 |
| temperature | varchar(50) | 设备温度 |
| humidity | varchar(50) | 环境湿度 |
| status | tinyint | 1正常 2异常 |
| description | varchar(500) | 巡检描述 |
| create_time | datetime | 创建时间 |
巡检记录表建议把温度和湿度直接设计成varchar类型,因为实际填写时可能是“正常”“偏高”这类文字描述,也可能是数值。用varchar的泛化能力更强,配合前端输入框的校验逻辑即可。
报修单和维修工单
报修单字段:报修人ID、设备ID、故障描述、紧急程度(1普通 2紧急)、报修时间、状态(0待审核 1已通过 2已驳回)、附件路径。
维修工单字段:工单编号、报修单ID、维修负责人、维修内容、更换配件、维修费用、开始时间、完成时间、验收人、验收结果、验收备注。
这两张表之间通过报修单ID建立一对一关系,工单编号可以做成“WX”+日期+流水号的形式,比如WX20250115001,这样显得规范,答辩时也是亮点。
2.3 数据库设计中的避坑经验
很多人在设计表的时候会忽略一个细节:时间字段的类型。Java实体类用LocalDateTime,数据库用datetime,这样MyBatis Plus在映射时不会出问题,避免Date类型带来的时区和格式化麻烦。
另外,逻辑删除字段建议所有主表都加上。MyBatis Plus提供了@TableLogic注解,实现逻辑删除只需要在实体类加一个deleted字段并注解,然后所有delete操作自动变成update。这个习惯在毕设里就能养成,以后进公司写代码会少踩很多坑。
外键约束这个坑我提一下。很多教材鼓励加物理外键,但在实际开发中,尤其是这种管理系统的场景,我更推荐用逻辑外键,也就是业务层面的事务控制,而不是数据库层面的物理外键。原因很简单:加了物理外键之后,删除设备和修改用户时会有很多动作牵连,排错和测试都很麻烦。用代码去控制数据一致性,自由度更高。
3. 前后端功能实现与关键代码解析
3.1 后端核心接口实现思路
后端接口设计遵循RESTful风格,统一返回结果封装成Result对象。这个Result对象里有code、message、data三个字段,前端根据code判断请求是否成功。
以“创建巡检记录”为例,我贴一段化简后的核心代码逻辑。
java复制@Override
public Result createInspectionRecord(InspectionRecordDTO dto) {
// 1. 参数校验
if (dto.getDeviceId() == null || dto.getInspectorId() == null) {
return Result.error("设备或巡检人员不能为空");
}
// 2. 校验设备状态
DeviceInfo device = deviceService.getById(dto.getDeviceId());
if (device == null) {
return Result.error("设备不存在");
}
if (device.getStatus() == DeviceStatus.REPAIRING.getCode()) {
return Result.error("当前设备维修中,暂不能执行巡检操作");
}
// 3. 保存巡检记录
InspectionRecord record = new InspectionRecord();
BeanUtils.copyProperties(dto, record);
record.setStatus(InspectionStatus.NORMAL.getCode());
record.setCreateTime(LocalDateTime.now());
this.save(record);
// 4. 回写设备信息(如温度等可提取到设备表)
device.setLastInspectTime(LocalDateTime.now());
deviceService.updateById(device);
return Result.success("巡检记录提交成功");
}
这段代码虽然简单,但体现了一个非常重要的分层思想:Controller层只做参数接收和结果返回,Service层做业务逻辑,Mapper层做数据持久化。很多同学写代码喜欢把数据库操作直接写在Controller里,短平快,但项目一复杂就会特别痛苦,改一个需求牵一发动全身。你在写毕设时一定要养成Service层处理的习惯,一方面代码清晰,另一方面答辩时老师问“你的业务逻辑写在哪里”你能对答如流。
3.2 前端界面设计与交互细节
如果用的是前后端不分离的Thymeleaf方案,页面结构相对传统,适合快速出活;如果用的是Vue + Element Plus的前后端分离方案,整体观感会更现代化,像真正的企业级项目。
我建议如果你时间富裕,优先尝试Vue + Element Plus,原因很简单:招人市场和老师眼里,前后端分离项目的“高级感”就是不一样。虽然工作量会增加,但你可以复用现成的后台管理模板,比如若依框架或者vue-element-admin,改一改页面和接口就能用。
我这里重点讲Vue前端实现中的几个细节。
第一,路由守卫。未登录时不能访问首页,登录后根据角色动态渲染菜单。这个功能在Vue Router里用beforeEach守卫实现,逻辑简单但效果很好。
javascript复制router.beforeEach((to, from, next) => {
const token = sessionStorage.getItem('token')
if (to.path !== '/login' && !token) {
next('/login')
} else {
next()
}
})
第二,Axios统一请求拦截。在请求头加入token,在响应拦截器里统一处理token过期和错误提示,这样每个页面的请求代码就不用重复写错误处理了。
javascript复制http.interceptors.response.use(
response => {
const res = response.data
if (res.code === 401) {
sessionStorage.removeItem('token')
router.push('/login')
Message.error('登录过期,请重新登录')
}
return res
},
error => {
Message.error(error.message)
return Promise.reject(error)
}
)
第三,状态标签的颜色映射。工单状态、紧急程度这些字段在数据库里是数字,页面上需要展示成不同颜色的标签。Element Plus的el-tag组件可以动态根据状态值设置type属性(success、warning、danger、info),视觉效果比纯文字好很多。
3.3 MyBatis Plus多表关联查询的简化写法
使用MyBatis Plus时,单表CRUD确实非常爽,不需要写XML,Service继承IService就有现成的方法。但一到多表关联查询,很多新手就傻眼了,跑去写连表SQL。
我的经验是分情况处理:如果只是简单的两表关联,用LambdaQueryWrapper写一个子查询就够了;如果涉及到三张表以上的复杂统计报表,直接写XML自定义SQL更高效。
比如“查询报修单同时显示报修人和设备名称”这种需求,我这里展示用Mock方式写的SQL参考写法:
xml复制<select id="selectRepairListWithDevice" resultType="com.example.vo.RepairVO">
SELECT
r.id,
r.device_id,
d.device_name,
d.device_code,
r.reporter_id,
u.real_name AS reporter_name,
r.fault_desc,
r.emergency,
r.status,
r.create_time
FROM repair_order r
LEFT JOIN device_info d ON r.device_id = d.id
LEFT JOIN sys_user u ON r.reporter_id = u.id
WHERE r.deleted = 0
<if test="status != null and status != ''">
AND r.status = #{status}
</if>
ORDER BY r.create_time DESC
</select>
这里用LEFT JOIN而不是INNER JOIN,是为防止报修单关联的设备或用户被逻辑删除后,报修单本身也查不出来。这种细节在面试时拿出来讲,理解层次完全不一样。
3.4 权限控制和登录认证
权限控制在管理类系统里是必选功能。我的建议是用Spring Security + JWT实现,这个组合在Java生态里属于标准答案,网上资料极多,遇到问题容易搜到解决方案。
具体设计如下:用户登录成功后,后端生成JWT token返回;前端把token存到sessionStorage或localStorage;每次请求在Authorization头携带token;后端通过过滤器拦截受保护的接口,解析token并获取用户身份和角色信息。
角色划分上,我设计了三类角色:
| 角色 | 权限范围 |
|---|---|
| 管理员 | 用户管理、设备管理、系统配置、所有业务数据查看 |
| 维修主管 | 报修审核、维修派工、工单验收、统计报表 |
| 普通员工(巡检员/仓管员) | 巡检记录填写、报修申请、查看自己的工作记录 |
在Spring Security的配置类中,用注解或者配置方法对接口URL和角色进行绑定。不建议把权限细化到按钮级别,那个工作量太大了,角色-接口粒度的权限控制完全够用。
java复制@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/login").permitAll()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.antMatchers("/api/repair/audit/**").hasAnyRole("ADMIN", "REPAIR_MANAGER")
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager()));
}
4. 开发环境搭建与完整部署流程
4.1 本地开发环境准备
部署环节是整个毕设里最容易卡住人的地方,很多同学代码写完了,结果败在了环境配置上。我这里给出我自己验证过的一套完美组合。
- JDK 1.8(不要用更高版本,除非你想体验踩坑的酸爽)
- Maven 3.8.x
- MySQL 5.7 / 8.0
- Node.js 16+(Vue前端需要)
- IDEA 2022+(Ultimate版,社区版也能用但少一些前端插件)
这里我要强调一下JDK版本选择。现在Oracle JDK已经更新到21了,但Spring Boot 2.7系列的官方支持就是JDK 8和11,用了JDK 17很容易出现各种不兼容。你在毕设阶段选JDK 8不丢人,大部分公司生产环境到现在还在用JDK 8,你提前适应企业现状反而是加分项。
4.2 Spring Boot工程创建与核心配置
创建工程推荐用Spring Initializr,地址是 start.spring.io。选择Java 8,Spring Boot 2.7.18,依赖勾选Spring Web、MySQL Driver、MyBatis Plus Framework(如果是用第三方依赖包,可以后续手动引入)、Spring Security、Validation。
配置文件里面有几个需要注意的点。我贴一个精简版的application.yml配置。
yaml复制server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/granary?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: yourpassword
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
global-config:
db-config:
id-type: auto
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
重点解释两个参数。第一,characterEncoding=utf8必须加,否则查询中文会乱码;第二,serverTimezone=Asia/Shanghai必须加,否则高版本MySQL驱动连接时区配置会直接报错。这两个参数几乎是新手必踩的坑,我在帮忙调试的时候十个里面有八个是这个原因。
还有一个坑,就是Spring Boot版本太高导致的依赖冲突。在项目实际使用Spring Boot 2.7.x版本时,网上流传的部分教学基于3.0+版本,对应代码里的 javax.* 包名与新版本中的 jakarta.* 包名不同。如果你用了3.x版本的教程代码,放到2.x的项目里会直接报找不到包。碰到这种报错,先检查你在代码里import的是 javax.servlet.* 还是 jakarta.servlet.*,把这里对齐就解决了一大半问题。
4.3 Maven打包与部署上线
项目开发完成后,打包部署是重头戏。后端工程在根目录下执行:
bash复制mvn clean package -DskipTests
打包完成后,在target目录下会生成一个 granary-system-0.0.1-SNAPSHOT.jar 文件。这个就是可直接运行的Spring Boot应用。
本地启动方式:
bash复制java -jar granary-system-0.0.1-SNAPSHOT.jar
如果要在服务器上部署,我推荐用宝塔面板 + 进程守护管理,或者直接写一个systemd服务脚本。如果你对Linux不熟,最稳妥的方式是装一个宝塔面板,可视化操作MySQL和Java环境,上传jar包后添加一个Java项目,设置端口和项目路径,点启动即可。
前端Vue项目的部署更简单,先执行打包命令:
bash复制npm run build
打包后的文件在dist目录,你把它部署到Nginx中,并配置反向代理,把接口请求转发到后端的8080端口。Nginx关键配置片段:
nginx复制server {
listen 80;
server_name your-domain.com;
location / {
root /www/granary/dist;
index index.html;
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
这里 try_files 的作用是解决Vue路由的history模式刷新404问题。前端部署的很多教程会漏掉这一行,导致你刷新页面就白屏,记得加上。
4.4 数据库初始化与初始化数据
项目数据库建议用SQL脚本方式初始化,脚本文件里包含建库语句、建表语句、基础数据插入语句。基础数据至少要包含一个管理员账号:admin / 123456(密码存BCrypt加密后的密文)。
有一点要特别提醒:如果你在代码里写了一个 DataInitializer 类,用 CommandLineRunner 在应用启动时自动创建管理员账号,那你一定要做存在性判断,否则每次重启都会重复插入数据,导致账号重复。我建议直接把初始化SQL放进项目resources目录下的 db/init.sql 里,手动执行一次就行,干净利落。
5. 核心功能演示流程与常见问题排查
5.1 演示时的标准操作顺序
毕设答辩时,演示系统的操作顺序最好经过设计,让老师跟着你的思路走,而不是东点一下西点一下。我建议按以下顺序演示。
- 打开登录页,输入管理员账号登录。
- 进入用户管理页面,新增一个巡检员账号,演示用户管理和角色分配。
- 进入设备管理页面,新增一台设备,填写设备编码、类型、位置。
- 进入巡检管理页面,以巡检员账号重新登录(或者管理员代录),创建巡检记录,填写温度和异常描述。
- 发现设备异常后,在报修管理页面发起一条报修申请。
- 切回管理员账号,审核这条报修申请,审核通过后自动生成维修工单。
- 在维修工单中派工给指定维修工,模拟维修工完成维修并填写维修结果。
- 管理员验收工单,设备状态自动从“维修中”恢复为“正常”。
- 最后展示统计报表页面,用图表展示本月巡检完成率、报修数量趋势。
这个顺序走了完整的一条业务闭环,每一步都有前因后果,老师想打断问问题也要先跟着你的节奏走,你就掌握了演示的主动权。
5.2 高频异常排查与解决方案
部署和运行这块,我整理了一个问题速查表,全是真实发生过的场景。
| 报错信息 | 产生原因 | 解决方案 |
|---|---|---|
| Access denied for user 'root'@'localhost' | 数据库密码错误或用户权限不足 | 检查application.yml中的密码,确认用户权限 |
| Table 'xxx' doesn't exist | 数据库脚本未执行或表名拼写错误 | 执行init.sql,检查实体类@TableName注解 |
| java.sql.SQLException: Unknown database | 数据库没创建 | 先执行 CREATE DATABASE granary |
| Invalid bound statement (not found) | Mapper接口和XML映射文件不匹配 | 检查XML文件路径和namespace,检查接口方法名与XML id一致 |
| 前端请求后端报404 | 请求路径错误或Controller未生效 | 查看后端控制台请求映射,核对URL |
| 中文乱码 | 数据库连接字符集配置缺失 | 在JDBC URL上加上characterEncoding=utf8 |
| JVM OutOfMemoryError | 启动时内存不足 | 用 java -Xmx512m -jar 指定内存 |
最常见的坑之一是idea导入Maven工程后,依赖一直报红。这个问题的根源基本都是Maven仓库配置不对。你在 settings.xml 里配置阿里云镜像,然后执行 mvn clean install -U 强制更新依赖,一般能解决。如果还不行,把本地仓库(默认在用户目录的.m2/repository下)里的报错相关文件夹删掉重新下载。
另一个常见问题是前端页面请求后端接口时跨域报错。后端CorsConfig配置类写得不对,或者前端代理配置遗漏,都会导致跨域。最简单的方案在后端加CorsFilter,一次性解决所有跨域问题:
java复制@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOriginPattern("*");
config.setAllowCredentials(true);
config.addAllowedMethod("*");
config.addAllowedHeader("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
5.3 部署上线时需要特别注意的三个细节
第一,MySQL的sql_mode问题。新版MySQL默认的sql_mode中包含ONLY_FULL_GROUP_BY,如果SQL语句里有 GROUP BY 且select字段不在group by里,会直接报错。建议在MySQL配置文件中把sql_mode设置为宽松模式,或者把项目中涉及group by的SQL改成兼容写法。我用EasyExcel做导出功能时踩过这个坑,排查了很久。
第二,文件上传路径问题。如果你的系统有上传附件功能,比如报修单上传故障照片,那么在Linux服务器上一定要处理好目录权限。路径如果写成绝对路径,换环境就失效;如果写在项目内,jar包部署时又找不到路径。我建议在配置文件中定义一个 upload.path 参数,本地开发指到本地目录,服务器部署指到服务器的 /data/granary/upload,启动前确认目录存在且有写权限。
第三,服务器防火墙和云安全组。不要在服务器上把所有端口都对外开放,只开放80(Nginx)和3306(数据库,如果内网访问则不开)。如果前端联调时发现连不上后端接口,检查安全组是否放行了8080端口,不要问我是怎么知道的。
6. 论文撰写思路与答辩应对策略
6.1 论文结构怎么安排
论文是毕设的另一半工作量,很多同学代码做得不错,论文却写得像流水账。我的建议是论文按以下结构来写。
第一章绪论,重点写研究背景和意义,不要大篇幅抄百度百科,要结合粮库设备管理的实际痛点写,比如“传统人工巡检效率低、纸质记录易丢失、维修进度无法跟踪”这些具体问题,然后引出系统的价值。
第二章相关技术介绍,这部分可以适度展开,把Spring Boot、MyBatis Plus、Vue、MySQL的核心特性写清楚,但不要过度堆砌。老师不关心你把官方文档抄了多少,关心的是“你为什么选这个技术”。
第三章系统分析,包含可行性分析(技术、经济、操作)、业务流程分析、功能需求分析、非功能需求分析。功能需求分析必备用例图,建议用PowerDesigner或者ProcessOn画。
第四章系统设计,包含总体架构设计、功能模块设计、数据库设计。数据库设计要附上ER图和每个表的字段说明,最好用表格展示。
第五章系统实现,按模块逐个展示关键代码和界面截图,代码不用全部贴,贴核心逻辑即可,每段代码后面必须有解释说明。
第六章系统测试,包含测试环境、测试用例表、测试结果。不要只写功能测试,加一点性能测试(用JMeter并发20个用户请求)和安全性测试,会显得很专业。
6.2 答辩中老师常问的问题与答题思路
我把常见答辩问题整理出来,每个问题我都给你一个答题方向。
问题一:为什么选择Spring Boot技术栈?
回答思路:Spring Boot简化了Spring的配置流程,内置Tomcat,能快速搭建独立运行的服务。同时它的生态完善,无论是数据持久化、安全认证还是消息队列,都有成熟方案。结合项目需求,管理类系统需要快速开发、稳定运行,Spring Boot是最合适的。
问题二:系统是如何实现权限控制的?
回答思路:用Spring Security框架实现认证和授权。登录成功后通过JWT生成token,前端请求时携带该token,后端通过过滤器对请求进行拦截验证token是否有效;根据token中携带的角色信息,判断用户是否有权限访问对应接口。不同角色对应不同权限,比如普通员工只能操作巡检和报修功能,管理员才能进行用户管理和设备管理。
问题三:报修和维修工单之间是什么关系?
回答思路:一对一的关联关系。报修申请通过审核后,系统会自动生成一张维修工单,工单携带报修单ID作为外键。维修工单的状态独立流转,不受报修单状态影响,这样能让业务拆解更清晰。状态机包括待派工、维修中、待验收、已完成、已关闭。
问题四:设备状态是如何更新的?
回答思路:设备状态不是随意修改的,而是由业务动作驱动。发起报修并审核通过后,设备状态由“正常”变为“维修中”;维修工单验收完成后,设备状态自动更新为“正常”。如果是巡检中发现设备异常,在提交巡检记录时同步生成报修申请,这也是联动的关键环节。
问题五:数据量大了怎么办?
回答思路:目前系统做了分页查询(MyBatis Plus分页插件),可以支撑万级数据量。后续扩展可以考虑:引入Redis缓存热门数据、数据库主从分离、对巡检记录等大表按月分区、使用消息队列削峰。答出两点以上就足够展示你的思考深度。
6.3 提升项目亮点的方法
如果你的时间还充裕,我强烈建议在基础功能之上加一个亮点功能,一个就行。这个亮点的价值在于写论文时多一章内容,答辩时多一个“创新点”,简历上多一行项目描述。
几个我推荐的亮点方向。
第一个方向是数据可视化。用ECharts做一个控制台首页,展示设备总数、维修完成率、巡检完成率、各类型设备报修占比等指标。这个功能不难实现,写几个统计SQL就行,但视觉效果提升极其明显。
第二个方向是消息通知。当报修申请审核通过时,给维修负责人发送系统通知(站内信或者WebSocket推送),实现“待办事项”提醒。用WebSocket做消息推送在面试时可以充分展开,是很好聊的点。
第三个方向是Excel导入导出。用EasyExcel把设备列表导入数据库,或者把巡检记录导出成Excel报表。粮库场景月度报表是很合理的需求,这个功能贴合业务且实用。
我个人最推荐第一个方向,因为统计页面一打开,视觉效果直接拉满,老师扫一眼就知道你做了不少工作。
7. 代码质量优化与个人经验总结
7.1 编写优雅代码的小习惯
毕设阶段很多人追求“代码能跑就行”,但我想说,代码质量和功能实现本身同等重要,至少在答辩时老师会翻你的代码。我总结几个能立竿见影提升代码观感的习惯。
第一,使用统一返回体。 不要有的接口返回字符串,有的返回Map,有的直接返回实体。统一用Result对象包装,前端处理逻辑会非常清爽。
第二,实体类不要直接暴露给前端。 用VO(View Object)接收前端参数、用DTO(Data Transfer Object)返回前端数据,不要图省事直接让Controller接收Entity。虽然多写几个类,但项目的代码层次感一下子就出来了。比如新增设备时,前端传来的值可能不含id和创建时间,你用Entity接收难道让前端传id吗?显然是错的。
第三,用枚举替代魔法数字。 设备状态值如果是1、2、3,不要散落在各个代码文件里写数字。定义一个枚举类DeviceStatus,然后通过 DeviceStatus.REPAIRING.getCode() 引用。好处是修改状态码时只改一处,而且代码可读性大幅提升。
第四,方法体控制在50行以内。 如果方法太长,一定拆分。拆分的粒度不用太追求完美,就是把独立逻辑块抽成私有方法,比如 checkDeviceStatus()、generateOrderNo(),方法名本身就是注释。
7.2 我在实际开发中踩过的坑
写了这么多年代码,遇到过的坑能绕地球一圈。下面这几个跟这个项目直接相关的,我展开说说。
第一个坑是事务失效问题。 MyBatis Plus的Service自带事务,但如果你在一个类中调用同类的另一个方法,事务注解会失效,因为Spring AOP默认不支持内部方法调用。比如“报修审核通过后同时更新报修单状态和创建设备维修中状态”这个方法,如果写成 this.updateStatus() 在同类内部调用,事务就可能不生效。解决办法有两个:一个是不调用同类方法,把这段逻辑拆到一个独立Service类中;另一个是通过 AopContext.currentProxy() 获取代理对象再调用。我在项目里用的第一种方案,逻辑最简单,也最容易跟面试官讲清楚。
第二个坑是逻辑删除与唯一索引冲突。 用户表的username字段设置了唯一索引,这时如果你逻辑删除一个用户后再新增同一个用户名的用户,数据库会报唯一索引冲突。因为逻辑删除只是update字段,数据还在表里。解决方案是:把唯一索引改成联合索引,即 (username, deleted),这样删除的数据因为deleted=1就不同时占用唯一索引了,可以再插入相同username的新记录。这个细节很小,但踩过坑之后印象极其深刻。
第三个坑是时区问题。 服务器部署后,发现创建时间比本地时间慢了8个小时。原因是应用默认用UTC时区读取当前时间,MySQL连接也用了UTC。修复方式是JVM启动参数加 -Duser.timezone=Asia/Shanghai,同时JDBC URL里加上 serverTimezone=Asia/Shanghai。做了这两步,时间就正常了。这也是我为什么在之前的配置文件里反复强调时区参数的原因。
第四个坑是Vue打包部署后页面空白。 把dist目录放到Nginx后,打开首页是空白的,控制台报 Uncaught SyntaxError: Unexpected token '<'。这个问题的根源是静态资源路径配置不对,Vue默认的 publicPath 是根路径 /,如果你部署在子目录下就会找不到文件。解决方案是在vue.config.js中设置 publicPath: './',用相对路径加载资源。毕设部署里如果直接放根目录可能遇不到这个问题,但如果你用了子目录部署,或者本地file协议打开dist文件,这个问题必然出现。
7.3 项目进一步扩展的方向
这个项目做完之后,你的Java Web开发能力基本可以达到初级开发者的水平。如果想更进一步,可以从这几个角度扩展。
技术层面,把Spring Security换成Sa-Token试试,两个框架对比一下,你对权限控制的理解会更深;把MySQL换成PostgreSQL跑一遍,感受不同数据库的差异;引入Redis缓存设备列表,压测工具JMeter测一测性能提升多少。
业务层面,增加备件管理模块,维修时消耗的配件从库存中扣除,库存低于阈值自动提醒采购;增加工作日历视图,在日历上展示巡检计划和维修工单,这个用FullCalendar插件实现,视觉效果很好;支持移动端H5访问,让巡检员在仓库现场用手机扫码设备二维码直接填报巡检结果。
工程层面,写单元测试,用JUnit 5 + Mockito把Service层的核心逻辑覆盖一遍,特别是状态流转相关的测试用例,这在面试中是个极好的加分点;用Dockerfile把应用做成Docker镜像,配合docker-compose一键启动MySQL和应用,把项目直接容器化。
我个人在做了几个类似项目之后最深刻的体会是:毕设项目的核心价值不在于用多新多炫的技术,而在于你把一个业务完整地理解并且实现了。Spring Boot这套体系学扎实了,后面学Spring Cloud微服务、分布式架构,都是在这个基础上做加法。
最后再分享一个小技巧:写代码的时候,尽量让方法名和注释表达出业务语言。比如 auditRepairOrderAndCreateWorkOrder() 这个名字,比 method1() 或者 updateRepair() 能传达更多信息。半年后你回头翻自己的代码,会感谢当时的自己。
