实验室管理系统毕业设计资源包:SpringBoot+Vue核心链路拆解

发布时间:2026/10/28 3:43:38
实验室管理系统毕业设计资源包:SpringBoot+Vue核心链路拆解 简介这份毕业设计资料包面向计算机相关专业正在完成毕设、课程设计或需要Java项目实战的学生围绕基于SpringBoot与MySQL的实验室管理系统展开提供一套可直接参考落地的完整项目方案。资源包约80.62MB上游文件总数标记为0且类型明细暂缺包内主要包含项目源码、数据库脚本、论文文档、开发说明与代码注释等源码采用SpringBoot后端、MySQL数据库和IntelliJ IDEA开发环境并经过严格调试确保可运行。系统功能覆盖登录注册、实验室与设备管理、实验室申请、设备报备、消耗品领取、新闻资讯及论坛信息等普通用户与管理员权限划分清晰。已有512人学习适合用来对照论文结构、梳理数据库设计、理解权限与业务流程也可作为课程设计、期末大作业或答辩准备的参考底稿对系统部署与功能演示也有帮助。1. 实验室管理系统这份答辩资源包里到底装了什么有个学弟去年找我说他毕设的题目是实验室管理系统代码写了一半论文一个字没动数据库还只有三张表。后来我给他看了这份资源包四件套齐全可运行源码、配套论文、MySQL 建库脚本、逐步截图的操作文档。设备台账、耗材出入库、预约审批、实验室排期、用户角色权限这几块业务都是通的前端 Vue Element UI后端 SpringBoot MyBatis-Plus数据库脚本能直接导入跑起来。适合两类人三周内要交稿答辩的计算机专业毕业设计选手和想把设备预约、库存扣减这类经典业务链路抄明白的后端新手。下面按「能干什么 → 怎么跑 → 怎么改」的顺序把这份资源拆开讲。2. 技术栈与目录结构一个前后端分离骨架怎么落到磁盘上拿到一份源码包第一件该做的事不是双击打开 IDE而是先看目录和依赖清单判断这套东西能不能在你的机器上启动、改起来要动几个文件。这个实验室管理系统的结构是典型的「后端出接口、前端做页面」分离形态没有微服务那套注册中心、网关的负担本地起两个进程就能跑对毕业设计这个场景来说是合适的复杂度。2.1 技术栈选型为什么这套组合是毕设的稳妥解这套组合的每一层都能在同学电脑上离线跑不依赖任何云服务也不要求在答辩现场连网。具体选型如下表。层次选型在项目里承担的职责后端框架SpringBoot 2.7提供 REST 接口内嵌 Tomcat一个java -jar就能起服务持久层MyBatis-Plus 3.5单表增删改查免写 XML统计类查询仍走手写 SQL数据库MySQL 8.0设备、预约、耗材、出入库、用户、角色等主表登录鉴权Spring 拦截器 JWT登录态校验、角色菜单过滤前端框架Vue 2 Element UI后台管理界面以表格 弹窗表单为主的 CRUD 页面构建工具Maven npm后端打 jar 包前端 build 出静态产物导出组件EasyExcel设备台账、预约记录导出 xlsx接口文档Knife4j答辩演示时可以直接在浏览器里点接口为什么不用 Spring Cloud因为这套系统的业务量是「一间实验室几十台设备、每天十几条预约」拆服务只会让启动和排错成本翻倍。我见过有同学硬上 Nacos Gateway结果答辩前一晚死在配置中心连不上这种事完全没必要。数据库选 8.0 也是因为窗口函数和 JSON 字段在统计报表里好用而且学校机房的 MySQL 现在基本都是 8.0。需要提醒的是 MyBatis-Plus 的版本要和 SpringBoot 对齐2.7 配 3.5.x 是稳的混进 3.4 以下的版本会在分页插件上报ClassNotFoundException。这类版本问题属于典型的环境玄学后面第 5 章会专门列。2.2 后端目录controller / service / mapper / entity 四层各管什么后端工程的目录是一眼能看懂的经典分层展开后大概是这样lab-system-server ├── src/main/java/com/lab │ ├── LabApplication.java # 启动类扫包从这里开始 │ ├── common │ │ ├── Result.java # 统一返回体 {code, msg, data} │ │ ├── BizException.java # 业务异常全局处理器接住 │ │ └── GlobalExceptionHandler.java │ ├── config │ │ ├── WebMvcConfig.java # 拦截器注册、跨域配置 │ │ └── MybatisPlusConfig.java # 分页插件 │ ├── controller # 只做参数接收和结果返回 │ │ ├── EquipmentController.java │ │ ├── ReservationController.java │ │ └── ConsumableController.java │ ├── service # 业务规则、事务边界都在这层 │ │ ├── impl │ │ └── ReservationService.java │ ├── mapper # 继承 BaseMapper复杂 SQL 写 XML │ │ └── ReservationMapper.java │ ├── entity # 与表一一对应的实体 │ └── dto # 前端传参对象与 entity 分开 ├── src/main/resources │ ├── application.yml # 数据源、端口、JWT 密钥 │ ├── mapper/ReservationMapper.xml # 手写 SQL │ └── sql/lab_system.sql # 建库建表 初始数据 └── pom.xmlcontroller层不要写业务判断只做三件事接参数、调 service、包Result返回。service层才是业务规则和事务的落点比如「预约时间段冲突检测」这种判断必须放在这里放到 controller 里会导致事务失效。entity和dto分开是个容易被忽略的细节很多毕设项目图省事直接拿实体接前端参数结果前端多传一个status字段就能把审批状态改了这是权限漏洞而不是代码风格问题。mapper层继承BaseMapperT之后selectById、insert、updateById这些方法不用自己写只有多表关联和复杂统计才需要落到resources/mapper/*.xml。这份资源的 XML 里主要就是两处预约冲突检测和耗材出入库的月度汇总。2.3 前端目录api / views / router 三个位置决定改哪里前端目录同样是按职责切开的lab-system-web ├── public ├── src │ ├── api # 所有 axios 请求集中在这一个模块一个文件 │ │ ├── equipment.js │ │ └── reservation.js │ ├── views # 页面一个路由对应一个目录 │ │ ├── equipment # 设备管理列表 编辑弹窗 │ │ ├── reservation # 预约管理我的预约、待审批 │ │ └── consumable # 耗材出入库 │ ├── router/index.js # 路由表 前置守卫做角色判断 │ ├── utils/request.js # axios 实例统一塞 token、统一处理 401 │ └── store # Vuex只存用户信息和菜单权限 ├── vue.config.js # devServer 代理解决本地跨域 └── package.json要加一个新页面动的位置是固定的三步api里加请求函数、views下加页面目录、router/index.js里挂路由。改字段展示只需要动对应views目录下的.vue文件不用碰后端。这个约定对新手很友好不至于改一个按钮颜色要翻五个文件。utils/request.js是全局的请求出口token 注入、错误码统一提示、401 跳登录都在这里改造的时候优先看这个文件能省很多事。2.4 配置文件跑起来之前必须改的四处后端能跑起来的关键在application.yml这份资源里已经写好了默认值但换到你自己的机器上至少四行要改server: port: 8081 # 改这里避开被占用的 8080 spring: datasource: url: jdbc:mysql://localhost:3306/lab_system?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root # 改成你本机的账号 password: 123456 # 改成你本机的密码 driver-class-name: com.mysql.cj.jdbc.Driver jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8 # 不加这行预约时间会整体差 8 小时 mybatis-plus: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true # 下划线字段自动映射驼峰属性 jwt: secret: lab-system-secret-key # 换掉别用默认值上线 expire: 7200 # token 有效期单位秒url里的serverTimezoneAsia/Shanghai和jackson.time-zone这两处必须同时设对只改一个仍然会出现列表里时间是对的、但提交后存进库差 8 小时的情况。map-underscore-to-camel-case打开之后数据库的equipment_id才能自动映射到实体的equipmentId关掉就得每个字段手写TableField。前端的vue.config.js里有一个devServer.proxy把/api前缀转发到http://localhost:8081这样本地开发就不需要后端开 CORS。代理配置和后端的 CORS 配置只保留一边两边都配反而会因为响应头重复导致浏览器报错这个坑在第 5 章细讲。3. 核心模块拆解一次设备预约从提交到库存扣减的完整链路整个系统里最能体现业务复杂度的不是增删改查而是预约。设备预约要处理三件事时间段不能冲突、库存不能被扣成负数、审批状态要能流转。这三件事任何一件写漏了答辩现场老师随便点两下就能暴露。这一章顺着「前端点提交」到「数据库落一条记录」的顺序把链路拆到底。3.1 预约状态机四个状态和它们之间的合法迁移预约单在数据库里用status字段表示状态取值约定是值状态允许的下一步操作0待审批管理员通过 → 1管理员驳回 → 2用户取消 → 31已通过使用完毕归还 → 32已驳回终态只能重新发起新预约3已结束终态归还时回补库存状态迁移是最容易写乱的地方。我见过一种写法是在 controller 里判断if (status 0) { ... } else if (status 1) { ... }然后直接在页面上放一个下拉框让用户随便选状态。这样做的后果是用户能把「已驳回」直接改成「已通过」等于审批形同虚设。合格的做法是把迁移规则收敛到 service 层的一个方法里用一张允许迁移的表来约束任何不在表里的迁移直接抛业务异常。这样无论是审批页面调、还是列表页调走的都是同一套校验。库存的扣减时机也要在这里定清楚提交预约时就预扣库存防止别人抢驳回或取消时回补。不要等到审批通过才扣否则两个用户同时提交、管理员都点通过库存就超了。3.2 后端接口ReservationController 与 Service 的分工先看 controller它的职责薄到只有参数接收和返回包装RestController RequestMapping(/api/reservation) public class ReservationController { Autowired private ReservationService reservationService; // 提交预约设备id、时间段、数量、用途由前端传入 // 用户id不从请求体取从 token 里解析防止伪造 PostMapping(/submit) public ResultLong submit(RequestBody Valid ReservationDTO dto, RequestHeader(Authorization) String token) { Long userId JwtUtil.getUserId(token); Long id reservationService.submit(dto, userId); return Result.ok(id); } // 审批只有管理员角色能进角色校验在拦截器里做 PostMapping(/approve) public ResultVoid approve(RequestParam Long id, RequestParam Integer pass, RequestParam(required false) String remark) { reservationService.approve(id, pass, remark); return Result.ok(); } }这里有两个设计点值得说。第一userId一定从 token 里拿不能信任前端传的值否则改个请求体就能替别人预约。第二Valid配合 DTO 上的注解做基础校验非空、长度、时间格式校验失败会被全局异常处理器统一转成{code: 400, msg: ...}前端只处理一种错误结构就行。真正干活的是 service方法上标Transactional任何一步抛异常整条链路回滚Service public class ReservationServiceImpl implements ReservationService { Autowired private EquipmentMapper equipmentMapper; Autowired private ReservationMapper reservationMapper; Override Transactional(rollbackFor Exception.class) public Long submit(ReservationDTO dto, Long userId) { // 1. 时间段合法性开始必须早于结束且不能约在过去 if (!dto.getStartTime().isBefore(dto.getEndTime()) || dto.getStartTime().isBefore(LocalDateTime.now())) { throw new BizException(预约时间段不合法); } // 2. 冲突检测同一设备、状态为 0 或 1 的时间段不允许重叠 int conflict reservationMapper.countConflict( dto.getEquipmentId(), dto.getStartTime(), dto.getEndTime()); if (conflict 0) { throw new BizException(该时间段已被占用请换一个); } // 3. 条件更新扣库存affected0 说明库存不够 int affected equipmentMapper.decreaseStock(dto.getEquipmentId(), dto.getNum()); if (affected 0) { throw new BizException(可用数量不足请刷新后重试); } // 4. 落预约单初始状态待审批 Reservation r new Reservation(); r.setUserId(userId); r.setEquipmentId(dto.getEquipmentId()); r.setStartTime(dto.getStartTime()); r.setEndTime(dto.getEndTime()); r.setNum(dto.getNum()); r.setStatus(0); r.setCreateTime(LocalDateTime.now()); reservationMapper.insert(r); return r.getId(); } }参数说明dto.getStartTime()和getEndTime()是LocalDateTime类型Jackson 按application.yml里配的格式反序列化countConflict的三个参数拼进 XML 的 SQLdecreaseStock的返回值是受影响行数这是判断并发是否抢到的唯一依据。执行顺序不能调换。如果先插预约单再扣库存扣库存失败回滚时虽然数据一致但自增 id 已经被消耗如果先扣库存再查冲突冲突检测失败会回滚看起来没问题但在高并发下会多出大量无谓的行锁等待。先查冲突读操作代价低、再扣库存写操作加锁范围小、最后插入是这条链路里我比较推荐的顺序。3.3 并发扣减条件更新 唯一索引双保险库存扣减是整个系统里唯一会被并发打穿的地方。假设两台设备、十个用户同时提交用「先 select 查出库存、Java 里减一、再 update 回去」的写法必然扣成负数。正确姿势是把判断和扣减合并成一条 SQL!-- EquipmentMapper.xml 片段 -- update iddecreaseStock update t_equipment set stock stock - #{num}, update_time now() where id #{id} and stock #{num} /update这条 SQL 靠where stock #{num}做条件判断MySQL 在执行 update 时会对这一行加排他锁affected 0就代表要么库存不够、要么行不存在两种情况都当失败处理。参数#{num}是本次预约的数量#{id}是设备主键。再补一层唯一索引防重复提交。用户手快连点两下提交按钮两笔请求可能都通过冲突检测因为第一笔还没插进去结果生成两条一模一样的预约。解决办法是在t_reservation上加一个联合唯一索引alter table t_reservation add unique key uk_equip_start_user (equipment_id, start_time, user_id);equipment_id、start_time、user_id三个字段组合唯一同一用户对同一设备同一开始时间只能有一条记录。第二笔插入会抛DuplicateKeyException被全局异常处理器接住返回「请勿重复提交」。前端也要配合提交按钮点击后立刻置loading true并禁用接口返回再恢复。这是最廉价也最有效的一层拦截。3.4 前端表单联动时间段置灰与库存提示后端校验是最后一道防线前端该做的提示不能省。预约弹窗里选定设备和日期之后先请求一次该设备当天的已占用时间段把冲突的选项置灰// views/reservation/components/ReservationDialog.vue export default { data() { return { disabledRanges: [], // 已占用的时间段格式 [[09:00,10:00]] stockLeft: 0 } }, methods: { // 设备或日期变化时调用刷新不可选时间段 async refreshOccupied() { if (!this.form.equipmentId || !this.form.date) return const { data } await getOccupiedSlots(this.form.equipmentId, this.form.date) this.disabledRanges data.map(s [s.startTime, s.endTime]) this.stockLeft data.stock }, // Element UI 时间选择器的禁用逻辑命中已占用区间就置灰 disabledTime(time) { const t time.format(HH:mm) return this.disabledRanges.some(([s, e]) t s t e) } } }getOccupiedSlots对应后端/api/reservation/occupied返回结构里顺带把剩余数量带回来页面上就能显示「当前可约 2 台」比提交之后再报错体验好很多。disabledTime里用的是HH:mm字符串比较前提是时间段都精确到分钟、且格式统一如果数据库里存的是完整时间戳这里要先格式化成同样的字符串再比否则会出现置灰不生效。这个模块拆完剩下的设备管理、耗材出入库、用户管理基本是同一套模板的变体看懂预约这条链路其余模块照着改就行。4. 数据库与论文E-R 图、建表脚本、功能模块图怎么对上很多同学写论文时系统设计那一章和实际代码是两张皮论文里的实体关系图画了八个实体数据库里只建了五张表论文写的模块名叫「设备报修管理」代码里压根没有对应类。答辩老师最容易在这里追问所以这一章专门讲怎么把数据库、代码、论文三者对齐。毕业设计论文里的数据库设计章节本质上就是这份建表脚本的文字版写的时候对着脚本抄比凭空想象靠谱得多。4.1 六张核心表与它们的关联关系这份资源的库名是lab_system核心表六张关系是-- 用户表一个用户一个角色简化成单角色够毕设用 create table t_user ( id bigint primary key auto_increment, username varchar(50) not null comment 登录名, password varchar(100) not null comment BCrypt 加密后存储, real_name varchar(50) comment 真实姓名, role_id bigint not null comment 角色id, status tinyint default 1 comment 1启用 0禁用, create_time datetime default current_timestamp ) engineInnoDB default charsetutf8mb4 comment用户表; -- 设备表stock 是可预约数量total 是总数 create table t_equipment ( id bigint primary key auto_increment, equip_name varchar(100) not null comment 设备名称, equip_no varchar(50) not null comment 设备编号唯一, lab_id bigint comment 所属实验室, total int default 0 comment 总数量, stock int default 0 comment 可预约数量, status tinyint default 1 comment 1正常 0停用, unique key uk_equip_no (equip_no) ) engineInnoDB default charsetutf8mb4 comment设备表; -- 预约表核心业务表 create table t_reservation ( id bigint primary key auto_increment, user_id bigint not null, equipment_id bigint not null, start_time datetime not null, end_time datetime not null, num int default 1 comment 预约数量, status tinyint default 0 comment 0待审批 1已通过 2已驳回 3已结束, remark varchar(255), create_time datetime default current_timestamp, key idx_equip_time (equipment_id, start_time) ) engineInnoDB default charsetutf8mb4 comment设备预约表; -- 耗材表、耗材出入库表、操作日志表结构类似此处略六张表的关联逻辑是t_user.role_id关联角色表t_equipment.lab_id关联实验室表t_reservation通过user_id和equipment_id两个外键把两边串起来。写论文的 E-R 图时实体就是这六张表联系就是外键一对一、一对多、多对多的判断按实际业务来一个用户可以有多次预约1:N一台设备可以被多次预约1:N用户和设备之间通过预约表形成 M:N。这里有个常见误区很多同学 E-R 图画得很漂亮菱形框、椭圆属性一应俱全但建表时忘了加外键或索引导致论文里写的「通过设备编号快速检索」根本没有对应的索引支撑。t_reservation上的idx_equip_time就是干这个的冲突检测的 SQL 会走这个索引数据量上千条时差别很明显。4.2 建表脚本里的三个约定脚本里能看出作者是有工程习惯的三个约定值得学。第一个是charsetutf8mb4。不要写utf8那在 MySQL 里是utf8mb3最多存三个字节遇到 emoji 或某些生僻字会直接插入失败报Incorrect string value。设备名称里带个特殊符号就炸排查起来很费时间。第二个是金额和数量字段全部用int/decimal不用float。耗材单价用decimal(10,2)库存用int避免浮点误差。第三个是状态字段全部用tinyint加注释不用中文枚举存字符串。中文状态在 Java 里映射枚举时要写一堆switch改起来麻烦而且数据库排序是按编码走的中文状态排序结果跟你想的不一样。配套的data.sql里插了初始数据一个 admin 账号、三个角色管理员、教师、学生、若干设备和耗材。导入顺序是先建库、再导lab_system.sql脚本开头有drop database if exists和create database所以重复导入不会报错这一点对反复调试很友好。4.3 论文功能模块图与代码目录的对应关系论文里的系统功能结构图画的是模块层级比如实验室管理系统 ├── 设备管理 │ ├── 设备信息维护 │ └── 设备状态变更 ├── 预约管理 │ ├── 预约申请 │ ├── 预约审批 │ └── 预约记录查询 ├── 耗材管理 │ ├── 耗材入库 │ └── 耗材出库 └── 系统管理 ├── 用户管理 └── 角色权限这张图要和代码目录严格对应设备管理对views/equipment和EquipmentController预约管理对views/reservation和ReservationController耗材管理对views/consumable系统管理对views/system。写论文的时候把这张图和目录截图放在一起老师一眼就能看出系统不是拼凑的。操作文档这块资源里的文档是按角色写的管理员登录后能做什么、普通用户登录后能做什么每一节配一张操作截图和一句说明。论文的「系统测试」章节可以直接复用这些操作步骤改写成测试用例比如「用例 3-1学生提交设备预约预期结果生成待审批记录实际结果通过」。这样操作文档和论文测试章节共用一份素材工作量减半。需要提醒一点论文里的截图最好重新截一遍用自己的数据。资源自带的截图是原始作者的界面直接贴进去遇到查重或答辩追问会说不清。换个设备名、换个用户名重截一遍成本很低但稳妥。5. 避坑排查从启动到数据错乱的五个真实坑这套系统我前后帮着调过几次遇到的问题高度集中在几个位置。下面五条按「现象 → 原因 → 解决」记录都是实际碰到过的不是纸上谈兵。5.1 启动报数据库连接失败Public Key Retrieval 与账号密码现象SpringBoot 启动时抛java.sql.SQLException: Access denied for user rootlocalhost或者报Public Key Retrieval is not allowed后者出现在密码明明正确的情况下。原因前一种是application.yml里的账号密码还是资源包默认值没改成你本机的。后一种是 MySQL 8.0 的caching_sha2_password认证插件在 JDBC 驱动里默认不允许客户端拿公钥需要显式开启。很多同学第一次装 MySQL 8 都会撞上这个。解决把application.yml的username/password改成你本机mysql -u root -p能登进去的那组然后在连接串后面补上allowPublicKeyRetrievaltrueuseSSLfalse。如果本机 MySQL 是 5.7把驱动类换成com.mysql.jdbc.Driver并去掉cj包引用。改完用 IDEA 的 Database 面板先连一次能连上再启动工程能省掉大量猜测。5.2 前端调接口一直 404 或报跨域代理和 CORS 只该改一边现象页面能打开但所有请求在 Network 面板里都是 404或者报has been blocked by CORS policy。原因vue.config.js里的proxy没配或者配了但请求路径没带/api前缀。另一种情况是后端WebMvcConfig里加了addCorsMappings前端代理也配了转发两边的Access-Control-Allow-Origin响应头重复浏览器照样拒绝。跨域和代理这两套方案是二选一不是叠加。解决开发阶段统一用代理后端把那行 CORS 配置注释掉。vue.config.js里写proxy: { /api: { target: http://localhost:8081, changeOrigin: true } }同时确认utils/request.js里baseURL是/api接口函数的路径不要再重复带/api否则变成/api/api/xxx。部署到生产环境时前端打包后跟后端同域跨域问题自然消失也不需要 CORS。5.3 预约时间差 8 小时时区要三处一起对现象前端选的 14:00列表里显示 06:00或者数据库里存的和页面显示的对不上。原因时区没对齐链路上有三个地方分别是 JDBC 连接串、Jackson 序列化、MySQL 服务端时区。只改一处剩下两处还在用 UTC就会整体偏移 8 小时。这是最典型的「玄学问题」看起来没逻辑其实每一处都有明确归属。解决三处一起改。JDBC 连接串加serverTimezoneAsia/Shanghaiapplication.yml里spring.jackson.time-zone: GMT8并指定date-formatMySQL 服务端确认select global.time_zone返回08:00或SYSTEM如果是00:00要改my.cnf或在连接串里强制指定。实体字段统一用LocalDateTime而不是DateJackson 对LocalDateTime的处理更可控。5.4 库存扣成负数条件更新没加或者加了没判断返回值现象压测或者多开几个浏览器同时提交设备可预约数量变成 -1、-2。原因写了update t_equipment set stock stock - 1 where id ?只按主键更新没有and stock #{num}这个条件也没有判断affected返回值。数据库层面没有任何拦截并发请求全部执行成功。解决把 SQL 改成条件更新见 3.3 的片段Java 里拿到affected 0立刻抛业务异常并回滚。改造完之后自己在两个浏览器窗口同时点提交验证一次这是最直接的验证方法。如果想更稳加一个定时对账任务每天凌晨把stock重新按total - 未结束预约数算一遍出现偏差就告警。5.5 导出 Excel 乱码、表头缺失文件名编码和 EasyExcel 注解现象导出的 xlsx 打开是乱码或者表头是英文字段名、列宽全部挤在一起。原因文件名里的中文没有做 URL 编码浏览器按 ISO-8859-1 解析表头没在 DTO 上标ExcelPropertyEasyExcel 就拿字段名当表头。解决响应头里用URLEncoder.encode(设备台账.xlsx, UTF-8)编码后再设Content-Disposition导出用的 DTO 每个字段加ExcelProperty(设备名称)和ColumnWidth(20)。还有一点response.getOutputStream()不要在write之后关两次EasyExcel 内部会处理手动关会导致文件截断、打开报「文件已损坏」。6. 二次开发把一份毕设资源改成能现场演示的业务系统资源拿到手能跑通只是及格线要让老师觉得「这是你自己做的」还得做几处能讲出理由的改造。我一般会优先动这三个位置。第一处是审批流的加签。原始逻辑是「管理员一点通过就结束」可以改成两级审批导师先审、实验室管理员终审t_reservation加一个approve_level字段0 待导师、1 待管理员、2 通过。改动集中在ReservationServiceImpl.approve一个方法前端在待审批列表加一列显示当前环节代码量不大但业务完整度明显提升答辩时也有的讲。第二处是预约日历视图。列表看时间段不直观用 Element UI 的el-calendar做一个月视图每天格子里显示当天的预约数量点进去看详情。这个功能不需要动后端接口复用已有的按日期查询接口就够属于前端性价比最高的加分项。第三处是操作日志切面。系统管理和设备变更这两类操作用 Spring AOP 切controller上带自定义注解的方法把操作人、方法名、参数、耗时写进t_oper_log。代码二十行左右但能撑起论文里「系统安全性设计」一小节Aspect Component public class OperLogAspect { Autowired private OperLogMapper operLogMapper; // 切所有标了 OperLog 的方法 AfterReturning(pointcut annotation(operLog), returning result) public void record(JoinPoint jp, OperLog operLog, Object result) { OperLogEntity entity new OperLogEntity(); entity.setMethod(jp.getSignature().getName()); entity.setParams(Arrays.toString(jp.getArgs())); // 参数过长要截断 entity.setOperTime(LocalDateTime.now()); operLogMapper.insert(entity); } }jp.getArgs()里如果混进了HttpServletRequest或文件流toString会抛异常上线前记得加一层类型判断和长度截断超过 500 字符截掉。改完之后怎么验证我习惯按这个顺序走一遍先用 Postman 单独打接口确认返回结构没变再在页面上走完整流程提交 → 审批 → 归还 → 库存回补最后开两个浏览器窗口并发提交同一时间段看是不是只有一个成功。这三步走完基本能覆盖 90% 的回归问题。说个我自己的教训。第一次改这类系统时我图快直接在entity上加字段、顺手把dto也删了结果前端多传的status直接把审批状态改了演示的时候被老师当场问住。从那以后我每次动业务字段都强制走一遍「dto 收参 → service 校验 → entity 落库」这条线再急也不省这一步。希望这份拆解能帮你少绕几个弯顺利把项目跑起来、讲清楚。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询