ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

实验室管理系统实战:Spring Boot + Vue前后端分离开发全解析

实验室管理系统实战:Spring Boot + Vue前后端分离开发全解析 实验室管理系统这个东西一听就是个典型的“课设热门题”但真把它做到能上线、能扛住真实使用场景其实坑不少。我最早接触这类项目是在帮一所高职院校做实训室信息化改造当时他们还在用Excel登记设备、微信群里接龙预约实验室月底对账能对到怀疑人生。后来用了Spring Boot Vue这套前后端分离的方案重写了一遍才算把流程理顺。这篇博文就围绕实验室管理系统这个项目把从需求拆解、数据库设计、后端接口、前端页面到部署上线的完整思路讲清楚。内容偏实战适合正在做课设、毕业设计或者刚入职想拿一个完整项目练手的Java开发。文章里我会穿插一些我在真实项目里踩过的坑和取舍逻辑这些是文档里查不到的。1. 项目需求拆解与整体设计思路1.1 实验室管理到底在管什么很多人一听“实验室管理系统”第一反应就是做个设备增删改查。但真去现场调研一圈你会发现实验室管理至少涉及三个角色、五种核心事务管理员管设备台账、管实验室开放时间、管人员权限、管耗材库存。教师/实验员发起预约、审批学生申请、登记设备借用、填报设备故障。学生查看空闲实验室、预约实验时间、提交实验报告、查询设备状态。事务层面就更琐碎了设备入库、领用、归还、报修、报废实验室的预约、排课、临时借用耗材的入库、领用、库存预警还有整个流程里的审批链。任何一个环节断了线下就得靠人肉打电话催。所以我在设计这个系统时一开始就没有把它做成单纯的CRUD而是围绕“预约—审批—使用—归还—统计”这条业务主线来拆模块。系统最终划分成六大模块用户与权限、实验室管理、设备管理、预约管理、耗材管理、数据统计。每个模块再往下拆子功能形成一张清晰的功能清单。1.2 技术选型为什么是Spring Boot Vue技术选型是这类项目最容易被低估的一步。不少同学直接上手SSHStruts Spring Hibernate或者JSP Servlet结果前后端代码耦合在一起改个页面都要重启Tomcat。我当时定下Spring Boot Vue这套组合核心就三个理由第一前后端分离可以并行开发。后端同学只管出接口前端同学只管调接口互不阻塞。对团队开发来说效率提升非常明显对个人做课设来说也方便分阶段自测。第二Spring Boot把配置简化到了极致。内置Tomcat、自动配置数据源、起步依赖一键引入比起SSH时代那一堆XML配置省下来的时间足够多写两个业务模块。第三Vue对新手足够友好。响应式数据绑定、组件化开发、Vue Router做前端路由配合Element UI组件库两三天就能把管理后台的界面搭出来而且颜值在线。注意如果你的项目要求必须用单体架构或者学校强制指定了某种老旧框架那还是以要求为准。但只要有选择空间Spring Boot Vue 是当前性价比最高的方案。1.3 系统模块划分与功能清单确定技术栈之后我习惯先画一张模块图把所有功能点列全再决定优先级。这个系统最终的功能清单大致如下模块核心功能说明用户权限登录、登出、JWT鉴权、角色权限控制分为管理员/教师/学生三种角色实验室管理实验室信息维护、开放时间设置、状态管理支持空闲/使用中/维护中三种状态设备管理设备台账、设备状态、设备借用/归还、报修记录每台设备关联所属实验室预约管理在线预约、审批、取消、我的预约列表支持按时间段预约冲突自动检测耗材管理耗材入库、领用、库存预警低于阈值时系统自动提醒数据统计实验室使用率、设备使用排行、预约趋势用ECharts展示图表这些功能全部做完工作量其实不小。建议如果你是在做课设优先保证预约管理和设备管理两个核心模块做到闭环其他模块可以做简化版但流程要通。2. 数据库设计与后端核心实现2.1 核心表结构设计数据库设计是这个项目的根基表结构没设计好后面写接口处处难受。我最终的库表设计可以浓缩为六张核心表用户表、实验室表、设备表、预约表、耗材表、审批记录表。外加一张角色表做RBAC权限控制。用户表user的核心字段包括id、username、passwordBCrypt加密存储、real_name、role用整数表示角色、phone、email、status。这里有一个关键取舍角色字段我直接用整数存储而不是建三张关联表。原因很简单——这个系统的角色是固定的三种不需要动态扩展用整数反而查询更快、逻辑更简单。预约表reservation是整个系统的业务核心字段设计得比较细CREATE TABLE reservation ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL COMMENT 预约人ID, lab_id BIGINT NOT NULL COMMENT 实验室ID, reservation_date DATE NOT NULL COMMENT 预约日期, start_time TIME NOT NULL COMMENT 开始时间, end_time TIME NOT NULL COMMENT 结束时间, purpose VARCHAR(255) COMMENT 预约用途, status TINYINT DEFAULT 0 COMMENT 0待审批 1已通过 2已拒绝 3已取消 4已完成, device_ids VARCHAR(255) COMMENT 关联设备ID逗号分隔, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );注意device_ids这个字段我把关联设备ID用逗号拼接存放。这在严格的关系型数据库规范里是“违规”的但在实际项目中预约单关联设备是一个低频查询场景专门建一张中间表反而增加了联表复杂度。这种“反范式”设计是典型的以业务场景为导向的取舍。提示只要是设备明细需要被独立统计的场景就老老实实建中间表。我这个项目里设备借用是跟着预约单走的没有独立的设备借用统计需求所以用逗号拼接足够。2.2 JWT登录与权限控制登录认证我选的是JWTJSON Web Token而不是传统的Session。原因很简单前后端分离架构下后端接口是无状态的JWT把用户信息直接编码进Token里后端验签通过就信任不需要在服务端存Session天然适合水平扩展。JWT的集成方式很直接。首先引入依赖dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.11.5/version /dependency然后写一个JwtUtil工具类负责生成Token和解析Token。核心逻辑就两个方法一个根据userId和role生成Token一个从Token里解析出登录用户信息。public class JwtUtil { private static final String SECRET_KEY your-256-bit-secret-key-change-in-production; private static final long EXPIRE_TIME 7 * 24 * 60 * 60 * 1000; // 7天 public static String generateToken(Long userId, String role) { return Jwts.builder() .setSubject(String.valueOf(userId)) .claim(role, role) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() EXPIRE_TIME)) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); } public static Claims parseToken(String token) { return Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(token) .getBody(); } }有了JwtUtil之后再用一个OncePerRequestFilter拦截所有接口请求做统一的Token校验。Spring Boot里推荐用OncePerRequestFilter搭配Spring Security也可以在Filter里手动放行不需要认证的接口。我建议用Spring Security框架虽然学习曲线稍陡但权限表达更清晰——比如一键配置哪些接口只需要ADMIN角色。实际开发中JWT的坑不在生成和解析而在过期时间的策略。很多项目直接把过期时间设成2小时导致用户体验极差。但设成7天又不安全Token泄露后风险很大。我通常的做法是短期Token2小时 长期Refresh Token刷新机制。在这个实验室管理项目里考虑到使用场景是校园内部Token有效期设7天外加一个简单的“单设备登录”校验——用户修改密码后所有Token失效可以通过在Redis里记录密码版本号来实现。2.3 设备预约接口的状态流转预约模块的接口设计是整个后端最核心的部分。我设计了四个核心接口提交预约申请POST /api/reservation审批预约PUT /api/reservation/{id}/approve取消预约PUT /api/reservation/{id}/cancel查询我的预约GET /api/reservation/my提交预约时最关键的业务逻辑是时间冲突检测。同一间实验室同一时间段只能存在一条未取消且审批通过的预约。这个判断不能用简单的等值查询要处理交叉区间public void checkConflict(ReservationRequest request) { LambdaQueryWrapperReservation wrapper new LambdaQueryWrapper(); wrapper.eq(Reservation::getLabId, request.getLabId()) .eq(Reservation::getReservationDate, request.getReservationDate()) .in(Reservation::getStatus, Arrays.asList(1, 0)) // 已通过和待审批都算占用 .and(w - w .lt(Reservation::getStartTime, request.getEndTime()) .gt(Reservation::getEndTime, request.getStartTime()) ); Long count reservationMapper.selectCount(wrapper); if (count 0) { throw new BizException(该实验室在所选时间段已被预约); } }这个SQL条件startTime 传入的结束时间 AND endTime 传入的开始时间是判断时间段重叠的经典写法能覆盖所有重叠情况——包括完全包含、部分重叠、首尾相接的边界情况。审批接口的状态流转逻辑也很清晰待审批状态可以流转到已通过或已拒绝已通过状态只能流转到已完成。这里不能简单做一个setStatus必须校验当前状态是否允许目标状态否则会出现“已拒绝的预约又被改成已通过”之类的脏数据。我当时是把状态流转规则写在一个枚举里每个Status都定义了允许流转的目标状态集合不符合流转规则直接抛异常。这个设计后面救了我好几次因为前端页面有几个入口可以反复提交审批操作。2.4 后端日志与异常处理日志这块很多人写项目时完全不重视出了问题只能到处打System.out.println。我从一开始就给项目加上了logback配置按天滚动、控制台和文件双输出、 error级别单独出一个日志文件方便排查。Spring Boot的日志配置其实很简单在application.yml里做基本设置然后在resources下放一个logback-spring.xml做精细控制logging: level: com.example.lab: debug org.springframework.web: info file: name: logs/lab-system.log配合一个全局异常处理器RestControllerAdvice所有业务异常统一返回{code: 400, message: xxx}不用在每个Controller里写try-catch。遇到不可预料的异常日志里打error级别接口返回“系统繁忙”这种友好提示而不是把堆栈信息直接抛给前端。这套组合拳能让联调阶段的沟通成本降低一半以上。3. 前端页面与交互实现3.1 项目初始化与工程结构前端我用Vue 2 Element UI如果是从0开始的新项目建议直接Vue 3 Element Plus组合更新社区资料也更前沿。但无论是哪个版本工程结构都建议按功能模块来组织而不是按文件类型堆叠src/ ├── api/ # 接口请求封装 │ ├── login.js │ ├── reservation.js │ └── device.js ├── assets/ # 静态资源 ├── components/ # 公共组件 ├── router/ # 路由配置 ├── store/ # Vuex状态管理 ├── views/ # 页面组件 │ ├── login/ │ ├── dashboard/ │ ├── reservation/ │ ├── device/ │ └── user/ ├── utils/ # 工具函数 │ ├── request.js # axios封装 │ └── auth.js # token管理 └── App.vue把接口请求独立到api目录是因为前端同事或者你自己后续要改接口地址、加统一参数时不需要翻页面代码改一个文件就够了。这种“集中管理”的思路在后端接口一多的时候特别香。3.2 Axios请求封装与拦截器axios的二次封装是前端项目的标配核心目的是统一处理三件事请求头加Token、响应状态码统一判断、401跳登录页。// utils/request.js import axios from axios import { Message } from element-ui import router from /router const service axios.create({ baseURL: process.env.VUE_APP_BASE_API, timeout: 10000 }) // 请求拦截器统一加Token service.interceptors.request.use( config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] Bearer token } return config }, error Promise.reject(error) ) // 响应拦截器统一处理业务码 service.interceptors.response.use( response { const res response.data if (res.code ! 200) { Message.error(res.message || 请求失败) if (res.code 401) { localStorage.clear() router.push(/login) } return Promise.reject(new Error(res.message)) } return res }, error { Message.error(error.message || 网络异常) return Promise.reject(error) } ) export default service这里有几个细节值得注意第一请求头里的Bearer前缀是约定俗成的规范后端解析时要注意兼容带前缀和不带前缀两种情况我后端就是一个正则把Bearer剥掉再解析。第二401的处理必须谨慎。不能直接跳登录页因为可能是Token过期而非用户主动退出。我这里的简化处理是先清空本地存储再跳转但如果你的项目需要“静默刷新Token”就得在这里配合Refresh Token机制做二次请求。第三超时时间10秒是个合理值既能避免接口卡死影响体验又不会因为校园网慢导致误判。3.3 核心页面实验室预约流程的前端实现预约页面是整个前端最复杂的一个页面包含实验室选择、时间段选择、设备选择、提交申请四个步骤。我拆成了三个子组件LabSelector实验室选择、TimePicker时间段选择、DeviceSelector设备选择父组件负责整体状态管理。时间段选择这里有个交互巧思——我没有用传统的下拉框而是把一天按上课节次切成若干个时间段用卡片让用户点选。后端只需要传开始时间和结束时间前端初始化时拼好时间段选项const timeSlots [ { label: 08:00-09:40, start: 08:00, end: 09:40 }, { label: 10:00-11:40, start: 10:00, end: 11:40 }, { label: 14:00-15:40, start: 14:00, end: 15:40 }, { label: 16:00-17:40, start: 16:00, end: 17:40 }, { label: 19:00-21:00, start: 19:00, end: 21:00 } ]提交预约前前端会先从后端拉取该实验室在所选日期已被预约的时间段然后把这些时间段在界面上置灰。注意这个“置灰”只是体验优化真正的冲突拦截逻辑必须在后端再做一次。因为前端校验只能防君子不能防小人直接调接口绕过页面的操作是防不住的。很多项目都栽在这上面以为前端置灰了就不用后端校验了结果并发请求一来就出数据冲突。3.4 权限控制与路由守卫前端权限控制的核心思路是根据登录用户的角色动态生成可访问的路由表。我用Vue Router的addRoutes方法在用户登录后根据角色动态添加路由同时配合全局前置守卫拦截未登录和越权访问。// router/index.js const routes [ { path: /login, component: Login, meta: { public: true } }, { path: /, redirect: /dashboard } ] router.beforeEach((to, from, next) { const token localStorage.getItem(token) const role localStorage.getItem(role) if (to.meta.public) { next() return } if (!token) { next(/login) return } // 根据角色过滤可访问路由 if (to.meta.roles !to.meta.roles.includes(role)) { next(/dashboard) return } next() })这套方案的优点是权限控制直观缺点是你必须在路由表里为每个页面声明需要的角色meta.roles。如果项目角色复杂、权限粒度细建议改成后端返回权限码列表前端用自定义指令v-permission控制按钮级权限。对实验室管理系统这个体量来说路由级控制就足够了。注意前端权限控制只影响页面展示真正的数据安全必须靠后端接口鉴权。前端隐藏了按钮不代表接口不能被直接调用后端所有写操作接口都要校验当前用户角色是否有权限执行。4. 关键功能场景落地从预约到统计的完整闭环4.1 预约全流程的时序拆解我习惯用一个完整的业务场景来检验系统这里走一遍“学生预约实验室做实验”的完整流程看看各个环节怎么协作。学生登录后进入预约页面选择目标实验室和日期系统读取该实验室的开放时间并加载已占用时间段学生点选一个空闲时段再勾选本次实验需要用到的设备填写实验用途提交预约申请。此时后端做三件事校验用户身份和权限 → 执行时间冲突检测 → 插入预约记录状态为“待审批”并关联设备。预约成功后教师或管理员登录系统在“预约审批”列表中看到这条申请如果确认实验内容和设备都没问题就点击通过状态流转为“已通过”同时给学生发送一条站内通知。如果认为该时段与课程安排冲突则拒绝并填写理由。学生在“我的预约”中看到审批结果到预约日期当天凭借预约信息和校园卡到实验室管理员处签到管理员在系统中将预约状态改为“已完成”。至此预约流程闭环。可以发现所有状态变更都走的是后端接口没有一步是线下手工改库的。这就是为什么状态流转规则必须写在后端——你要保证无论前端以什么顺序调用状态机都不会出现非法跳转。4.2 审批逻辑与超时自动处理审批在真实场景里有个容易被忽略的问题如果教师一直不审批预约状态会一直停在“待审批”把时间段占着不放。学生想换个时间也换不了因为时间冲突检测把待审批状态也算作占用。我给出的解决方案是后台定时任务自动处理超时预约。使用Spring Boot自带的Scheduled注解每小时扫描一次把距离预约日期开始时间不足2小时仍未审批的预约单自动标记为“已取消”并释放该时间段。这个逻辑很多课设项目都会漏掉但这个需求在学校里非常真实——老师不可能24小时盯着审批页面。Component public class ReservationTimeoutTask { Scheduled(cron 0 0 * * * ?) // 每小时执行一次 public void autoCancelTimeoutReservations() { LambdaQueryWrapperReservation wrapper new LambdaQueryWrapper(); wrapper.eq(Reservation::getStatus, 0) // 待审批 .lt(Reservation::getStartTime, LocalTime.now().plusHours(2)) .eq(Reservation::getReservationDate, LocalDate.now()); ListReservation list reservationMapper.selectList(wrapper); for (Reservation reservation : list) { reservation.setStatus(3); // 已取消 reservation.setRemark(系统自动取消审批超时); reservationMapper.updateById(reservation); } } }定时任务在开发环境可以用但生产部署时记得在配置文件里用EnableScheduling注解的开关控制或者直接设置Spring的profile避免多实例部署时同一个任务被多个节点重复执行。如果不做分布式锁可以考虑用数据库行锁或者Redis的setnx来实现这是生产环境和高并发场景下的必修课。4.3 数据统计与可视化统计功能是管理员的刚需核心是三个指标实验室使用率、设备借用排行、预约量趋势。这些数据如果直接查数据库也能算但性能差且SQL复杂。我的方案是在数据库层面做聚合查询 前端用ECharts展示。后端提供三个接口GET /api/stats/lab-usage?month2025-05GET /api/stats/device-rank?limit10GET /api/stats/reservation-trend?days30例如实验室使用率计算逻辑是“该实验室当月被预约的总时长 ÷ 当月总开放时长”我直接在SQL里算好SELECT lab_id, SUM(TIMESTAMPDIFF(MINUTE, start_time, end_time)) AS used_minutes FROM reservation WHERE status 1 AND reservation_date BETWEEN #{startDate} AND #{endDate} GROUP BY lab_id然后在Java服务层再把used_minutes除以每个实验室的开放时长得到使用率。前端拿到数据直接喂给ECharts一个饼图展示不同实验室的使用率对比一个折线图展示30天的预约趋势。这套“后端聚合算好前端只做渲染”的模式避免了前端处理复杂计算的尴尬也让接口数据对多端复用友好。5. 常见问题与排查技巧实录5.1 跨域问题前后端联调第一道坎前后端分离项目跨域是必然遇到的第一个问题。前端跑在8080端口后端跑在9090端口前端发起的请求会被浏览器拦截。解决方案有三种第一种后端加CORS配置Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }第二种前端用Vite或Webpack的proxy代理转发。第三种部署时用Nginx统一转发。我的建议是开发环境用proxy代理生产环境用Nginx后端CORS配置是一种兜底方案。这里要特别提醒一个坑如果你用了Spring SecurityCORS配置必须在Security的过滤链里也放行OPTIONS请求否则预检请求会被拦截前端依然报跨域错误。这个问题我排查过整整一个下午最后发现是Security的csrf校验把OPTIONS请求挡了。5.2 动态菜单刷新后消失用addRoutes动态添加路由的另一个常见坑是用户按F5刷新页面后路由表恢复初始状态动态添加的路由全部丢失用户直接访问子页面就白屏了。解决方案是将用户角色信息持久化到localStorage在路由守卫里判断如果当前store中没有路由数据、本地又有角色信息则重新调用后端获取权限码并重新生成路由表。核心代码就是每次刷新时检测一次router.beforeEach(async (to, from, next) { const token localStorage.getItem(token) if (!token) { next(/login); return } const store useStore() if (!store.getters.hasRoutes) { const role localStorage.getItem(role) const dynamicRoutes generateRoutesByRole(role) store.commit(SET_ROUTES, dynamicRoutes) router.addRoutes(dynamicRoutes) next({ ...to, replace: true }) // 重新进入当前路由 return } next() })关键是next({ ...to, replace: true })这一步它的作用是让路由表更新完成后重新匹配一次否则刷新后第一次跳转的目标路由是空的。5.3 预约时间冲突的并发问题前面提到的时间冲突检测单机部署时用前面的查询逻辑就够了。但如果多个用户同时提交同一时间段的预约可能两个请求都通过了冲突检测然后同时插入数据导致“超卖”。解决办法有两个层面数据库层面给预约表加唯一索引比如针对lab_id reservation_date start_time end_time status建立一个联合唯一索引重复插入直接报错。不过这依赖索引字段的精确匹配如果时间区间有交叉但不完全相同唯一索引就失效了。更稳妥的做法是引入Redis分布式锁把“检查冲突 插入预约”这段逻辑用锁包起来。锁的key设计成reservation:{labId}:{date}获取锁成功后才执行查询和插入执行完成释放锁。这样即使并发再高同一个实验室同一天的预约请求也是串行处理的。String lockKey reservation: labId : reservationDate; boolean locked redisLock.tryLock(lockKey, 5, TimeUnit.SECONDS); if (!locked) { throw new BizException(当前操作人数较多请稍后重试); } try { checkConflict(request); reservationMapper.insert(buildEntity(request)); } finally { redisLock.unlock(lockKey); }对于课程设计级别的项目你做到这个粒度其实已经超过了绝大多数同题作品但如果系统部署在集群环境这个锁还必须换成Redisson的分布式锁才能保证跨节点互斥。5.4 前端播放需求上的“非主流”难题有朋友在做这类系统时会在前端加一个“上传实验视频回放”的功能要求页面里能直接预览视频。这里有个常见需求是播放m3u8格式的直播流或切片视频传统的video标签不支持必须引入hls.js或者使用video.js的hls插件来处理。这个跟实验室管理系统的核心业务无关但确实是不少学校在实训环节的硬性需求。以hls.js为例核心用法是import Hls from hls.js if (Hls.isSupported()) { const video document.getElementById(video) const hls new Hls() hls.loadSource(http://your-server/live/stream.m3u8) hls.attachMedia(video) hls.on(Hls.Events.MANIFEST_PARSED, () video.play()) }注意m3u8播放最关键的坑是跨域和CORS。视频服务器必须正确配置CORS头否则即使hls.js加载了m3u8请求分片TS文件时也会被浏览器拦截。另外如果想要免插件直接播放现代浏览器对HLS的原生支持仍然不够必须要走hls.js这条路。这个功能建议放在“设备管理—实验录像回放”子模块中作为实验室视频监控的补充来规划。6. 部署上线要点与性能优化经验6.1 双端打包与环境配置开发完成后部署是另一个大坑。前端项目执行npm run build生成dist目录里面是纯静态资源。后端项目执行mvn clean package生成一个可执行Jar包。生产环境的推荐做法是用Nginx托管前端静态文件同时把后端接口反向代理到Spring Boot进程上。Nginx配置核心就是一段location规则server { listen 80; server_name your-domain.com; root /var/www/lab-system/dist; index index.html; # 前端路由history模式配置 location / { try_files $uri $uri/ /index.html; } # 后端接口代理 location /api/ { proxy_pass http://127.0.0.1:9090; 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模式时刷新某个子路由页面比如/reservation/listNginx会先找对应文件找不到就统一返回index.html让前端路由接管。如果不配这一行刷新直接404。后端Jar包启动时我一般用shell脚本管理进程设好JVM参数和profile#!/bin/bash JAR_NAMElab-system-1.0.0.jar APP_HOME/opt/lab-system nohup java -Xms512m -Xmx1024m \ -jar $APP_HOME/$JAR_NAME \ --spring.profiles.activeprod \ $APP_HOME/logs/app.log 21 内存参数Xms和Xmx设成相同值可以避免运行期JVM频繁扩容收缩引起的性能抖动这是生产环境的一个小窍门。6.2 数据库连接池与查询优化Spring Boot 2.x以上版本默认使用HikariCP连接池配置很简洁但很多人都是直接用默认值。在实验室管理这种低并发场景默认配置够用但如果要应对选课高峰期几百个学生同时使用建议显式配置spring: datasource: hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000 idle-timeout: 600000对于查询效率我在预约列表页加了一个复合索引(user_id, reservation_date)因为“我的预约”总是按日期倒序展示。设备查询表则加了(lab_id, status)索引这是按实验室筛选设备的高频路径。统计模块的查询如果数据量大了一定要用定时任务把前一天的统计数据计算好存到统计表里而不是每次实时去扫全表。我最初版本就是实时聚合结果数据量到两三万条时查询开始变慢后来改成每天凌晨跑批计算接口响应时间从2秒降到了200毫秒以内。6.3 关于安全加固的几条底线建议实验室管理系统虽然定位是校内系统不直接暴露在公网但敏感数据安全底线还是要守住。第一密码绝对不允许明文存储。使用BCrypt加盐哈希Spring Security自带BCryptPasswordEncoder一行代码的事情不要偷懒。第二数据库账号不要用root单独建一个账号只授权业务数据库的增删改查权限。很多课设项目的数据库配置都是root/123456这在生产环境等于把家门钥匙挂在门口。第三生产环境务必关闭Spring Boot的actuator端点或者只暴露health端点。曾经有团队把项目部署到服务器上/actuator/env端点未关闭攻击者直接获取了服务器环境变量和配置信息。这类问题的教训太多了配置里几行代码就能避免。第四文件上传要做类型白名单校验。实验室管理系统里有实验报告上传功能如果不限制只允许上传docx、pdf等类型攻击者可以传一个jsp木马。用Apache Tika检测文件的真实类型不要相信文件后缀名。写在最后的体会项目做完之后我最大的感悟是实验室管理系统这类“课设常青树”题目恰恰是最容易流于表面的——网上一搜一堆源码但大部分只做了设备CRUD预约流程不是缺审批就是缺冲突检测统计模块更是几乎没人做。真把业务闭环跑通、把状态流转和并发冲突处理清楚的项目反而成了少数。我个人在做这个项目的过程中踩得最深的坑有两个一个是预约冲突检测起初只做了等值查询漏掉了交叉区间直到联调时被测试同学用“8点到10点和9点到11点”这个用例打回来另一个是前端路由刷新白屏排查了很久才意识到是动态路由初始化顺序的问题。这两个坑让我深深明白业务逻辑的严谨性永远比代码量更重要。如果你准备做或者正在做类似系统建议按“先闭环、再优化、后扩展”的顺序推进。先把预约—审批—使用—归还这条主链路走通再考虑加视频回放、消息推送、数据大屏这些锦上添花的功能。另外既然用了Spring Boot和Vue面试时大概率会被问“你们这个项目的权限是怎么做的”“预约并发冲突怎么解决”“为什么用JWT不用Session”这篇文里的几个设计点能不能讲清楚基本就决定了面试官对你的评价是“用过框架”还是“理解了项目”。最后再分享一个小技巧把项目的启动脚本、部署文档、接口文档全部整理进一个项目wiki里哪怕是自己个人的课设项目。因为这类系统通常需要持续迭代——这学期加了设备巡检下学期可能就要加耗材二维码扫码领用没有文档三个月后的你自己就是最陌生的开发者。
返回列表