ARTICLE DETAIL

资讯详情

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

基于Spring Boot和Vue的校园体育器材管理系统设计与实现

基于Spring Boot和Vue的校园体育器材管理系统设计与实现 做校园体育器材管理系统这个项目前后花了一周多的时间。最初是帮学弟搞定毕业设计他那边学校体育器材的借用、归还、损坏登记全走纸质表格期末盘点的时候要翻好几个本子才能算清楚器材坏没坏、丢没丢、被谁借走了都是糊涂账。顺着这个需求我基于Spring Boot做了一套校园体育器材管理系统把器材台账、借用审批、归还核验、报修处理、数据统计全部搬到了线上正好也适合作为Java方向的计算机毕业设计选题。这套系统的技术栈非常经典Spring Boot做后端MyBatis Plus操作数据库MySQL存数据前端用Vue搭界面。功能覆盖了器材管理、用户管理、借用流程、审批流程、报修流程、统计报表一个典型的后台管理系统应该有的东西基本都齐了。无论是准备写毕业论文、需要快速出一个能演示的完整项目还是想练手Spring Boot开发这个题目都能撑得住场面。1. 项目背景与整体设计思路1.1 校园体育器材管理的真实痛点先说说为什么要做这个东西而不是随便找一个网上的管理系统模板改一改。我去了解了一下大多数学校体育器材管理的现状是器材室有个管理员手里一本登记簿谁来借就手写记录器材名称、数量、借用时间、归还时间全看管理员心情填得规范不规范全看借的人自觉。等到期末或者运动会前要盘库存就只能把本子翻出来一页一页对费时费力不说还容易出错。更麻烦的是损坏的器材没有统一的报修渠道管理员可能过了一两周才发现某个篮球漏气了、某个羽毛球拍线断了这时候连是谁弄坏的都说不清楚。实际业务里的需求其实可以归纳成四点器材台账数字化每件器材有唯一编号、所属分类、存放位置、当前状态打开系统就能看到整个器材室的底数。借用流程线上化学生或者老师要借器材线上提交申请管理员审批审批通过后才能领取整个流程有记录、可追溯。库存动态可查一件器材被借走之后状态立刻变成“已借出”其他人看到的是实时状态不用再跑到器材室问“还有没有球拍”。统计报表自动化哪些器材借出频率最高、哪些器材损坏率最高、每个月有多少笔借用记录这些以前要靠人肉统计的数据现在系统直接生成。从毕业设计的角度看这四个需求点就是四个现成的功能模块每个模块都能展开写进论文的需求分析和系统设计章节不会出现没东西可写的尴尬。1.2 功能模块与角色权限划分系统按照使用人群分了三种角色普通用户、器材管理员、系统管理员。普通用户是学生或者老师主要用来查器材、借器材、还器材、报修器材管理员负责审批借用申请、办理归还、登记损坏系统管理员管理用户账号和系统基础配置一般就是学校信息中心的老师。功能模块我拆成了下面几张表模块功能点说明用户模块注册、登录、个人信息维护密码加密存储区分角色器材管理添加器材、编辑器材、删除器材、分类管理支持按名称、分类、状态筛选借用管理申请借用、审批、归还、逾期提醒核心流程涉及状态流转报修管理提交报修、处理报修损坏器材进入维修状态统计报表器材借用排行、分类统计、状态统计用ECharts展示图表系统管理用户管理、角色管理、数据字典主要是管理员维护基础数据这三种角色在接口权限上是隔离的普通用户调不了管理员的接口管理员也改不了系统配置。毕业设计答辩的时候老师很喜欢问权限是怎么控制的这个设计就是回答的基础。2. 技术选型思考为什么是Spring Boot Vue2.1 Spring Boot版本与Java环境怎么选很多第一次做Spring Boot项目的同学第一步就卡在版本选择上。我的建议是直接选Spring Boot 2.7.x JDK 8别去追新用Spring Boot 3.x除非你已经很熟悉Spring生态了。原因很现实学校的机房里装的大部分还是JDK 8教科书和网上教程也大部分基于Spring Boot 2.x写的遇到报错容易搜到答案。Spring Boot 3.x虽然更新但它要求JDK 17以上而且一些第三方框架的兼容性还需要额外适配对于毕业设计这种求稳的场景没必要给自己挖坑。另外Spring Boot 2.7是2.x系列的最后一个主流版本官方维护时间够长稳定性也很不错。用IDEA新建项目的时候选择Spring InitializrServer URL可以指到阿里的镜像地址这样依赖下载速度快、不容易超时。Java版本选8打包方式选jar引入Spring Web、MyBatis Plus、MySQL Driver等依赖。如果你的网络环境下载Maven依赖很慢就配一个阿里云的Maven镜像放到settings.xml里这个步骤能省下一大半的等待时间。2.2 后端框架、持久层与前端脚手架组合后端框架没什么好纠结的就是Spring Boot Spring MVC这是目前Java后端最主流的一套组合。Spring Boot负责自动配置把以前Spring和SpringMVC里繁琐的XML配置全部干掉项目启动就是一个内嵌Tomcat的Jar包部署也方便。持久层我选的是MyBatis Plus而不是原生MyBatis。原因是MyBatis Plus把单表的增删改查都封装好了写代码的时候只需要继承BaseMapperT常用的方法全是现成的可以省去编写大量重复SQL的时间。毕业设计项目规模不大单表操作是主体MyBatis Plus的LambdaQueryWrapper写动态条件非常顺手比如按器材名称模糊查询、按借用状态筛选几行代码就搞定。我一开始用原生MyBatis写过一个版本每写一个查询就要配一条SQL语句工作量直接翻倍。前端用的是Vue Element UI。Element UI的表格、表单、下拉框、日期选择器组件都很成熟适合快速搭后台管理界面。如果你对前端不太熟悉也可以直接用Vue CLI脚手架快速初始化一个Vue 2项目界面抄一下vue-element-admin模板重点是保证页面能用、接口能通。另外如果你只想要一个能跑通的后端项目不想写前端页面也可以用Spring Boot自带的Thymeleaf模板引擎渲染服务端页面但对答辩演示来说前后端分离的方案会显得更完整。前后端交互统一走RESTful API返回JSON格式的数据。为了规范我封装了一个通用的返回类ResultTpublic class ResultT { private Integer code; private String msg; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMsg(操作成功); result.setData(data); return result; } public static T ResultT error(String msg) { ResultT result new Result(); result.setCode(500); result.setMsg(msg); return result; } }这样前端收到的响应格式统一处理起来非常省事code200就走成功逻辑否则弹错误提示。2.3 项目初始化与基础配置项目创建好之后第一步是改application.yml把数据源、MyBatis Plus、日志相关的配置全部写好。这里贴一份我实际在用的配置server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/sport_equipment?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8 servlet: multipart: max-file-size: 10MB max-request-size: 20MB mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: id-type: auto logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0这些配置看起来多其实每一条都有实际作用map-underscore-to-camel-case: true开启下划线转驼峰命名映射。数据库字段是create_timeJava实体里就写成createTimeMyBatis Plus会自动映射省掉一堆resultMap。logic-delete-field: deleted开启逻辑删除。器材被删除的时候实际上是把deleted字段改成1而不是物理删除记录这样历史借用记录还能追溯。serverTimezoneAsia/Shanghai解决MySQL连接时报时区错误的问题。Jackson的时间格式化统一接口返回的日期格式避免前端拿到的是2025-05-10T08:00:00.00000:00这种不好看的时间戳字符串。3. 数据库设计与核心功能实现3.1 数据表设计六张表的细节数据库设计是毕业设计项目里最核心的一环。表设计得好不好直接决定后端开发的时候是顺滑还是痛苦。我这个系统一共设计了六张表每一张表都对应一个明确的功能模块。sys_user用户表字段类型说明idbigint主键自增usernamevarchar(50)登录名唯一passwordvarchar(100)加密后的密码nicknamevarchar(50)昵称rolevarchar(20)角色标识USER/ADMINphonevarchar(20)手机号deletedtinyint逻辑删除标记create_timedatetime创建时间equipment_type器材分类表字段很简单就是id、type_name、remark。为什么要单独弄一张分类表因为器材种类不是固定的今天可能是球类过段时间可能想增加健身器材这个分类独立一张表才能灵活扩展。equipment器材表是核心表字段类型说明idbigint主键type_idbigint关联分类表namevarchar(100)器材名称codevarchar(50)器材编号如EQ0001specvarchar(100)规格型号statustinyint状态0可借1已借出2维修中3已报废locationvarchar(100)存放位置pricedecimal(10,2)采购价格purchase_datedate采购日期covervarchar(255)器材图片地址deletedtinyint逻辑删除标记borrow_record借用记录表字段类型说明idbigint主键equipment_idbigint器材IDuser_idbigint借用人IDborrow_timedatetime实际借出时间expect_return_timedatetime期望归还时间actual_return_timedatetime实际归还时间statustinyint状态0待审批1审批通过2已拒绝3已归还4已逾期audit_user_idbigint审批人IDcreate_timedatetime申请时间repair_record报修记录表和reservation预约表也差不多就是记录谁报修的、什么器材、处理到什么状态、预约的日期和时段是什么。这里有个细节值得说一下所有表都没有设置物理外键只保存逻辑关联的id字段。这样做的好处是插入、更新数据时不需要数据库去额外校验外键约束性能更好而且后面做分页查询、逻辑删除的时候不会出现复杂的级联问题。毕业设计里逻辑关联已经完全够用了没必要给自己增加物理外键的负担。3.2 器材借用归还的核心流程器材借还是整个系统的主线业务流程也是答辩时讲解的重点。我梳理一下完整的流程第一步用户登录系统首页或者器材列表页浏览器材看到状态为可借的器材点击借用申请填写预计归还时间提交申请。此时器材的状态不能立即改成已借出因为管理员还没审批所以借用的记录状态是待审批。第二步器材管理员登录后台在借用审批列表里看到新申请点击查看详情。如果同意借用记录的状态变成审批通过器材的状态同时更新为已借出如果不同意填写驳回原因借用记录状态变成已拒绝器材状态不受影响。第三步使用者用完后到器材室归还。管理员检查器材有没有明显损坏没问题就登记归还借用记录的状态变成已归还写清实际归还时间器材状态改回可借如果有损坏就引导使用者提交报修申请器材状态改为维修中。第四步系统在归还日期到期前对还在借用且未归还的记录给出提示管理员可以联系借用人员催还。整个流程的核心就是一个状态机。器材状态和借用状态是两个维度必须区分开不能混淆。很多初学者容易犯一个错误把借用记录的状态直接等同于器材的状态。实际上一件器材被申请了但是管理员还没审批器材本身的状态仍然是可借只有在审批通过那一刻器材状态才被改成已借出。搞清楚这一点设计数据库和写接口的时候就会清晰很多。3.3 核心代码实现与状态管理后端代码的分层结构是标准的Controller-Service-Mapper我贴一段核心的器材实体类和查询接口来展示实现思路。器材实体类Data TableName(equipment) public class Equipment { TableId(type IdType.AUTO) private Long id; private Long typeId; private String name; private String code; private String spec; private Integer status; private String location; private BigDecimal price; private LocalDate purchaseDate; private String cover; TableLogic private Integer deleted; }器材分页查询接口RestController RequestMapping(/api/equipment) public class EquipmentController { Resource private EquipmentService equipmentService; GetMapping(/page) public ResultIPageEquipmentVO page(RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, EquipmentQuery query) { return Result.success(equipmentService.selectEquipmentPage(pageNum, pageSize, query)); } }Service实现类中的核心查询逻辑用MyBatis Plus的LambdaQueryWrapper构造动态条件Override public IPageEquipmentVO selectEquipmentPage(Integer pageNum, Integer pageSize, EquipmentQuery query) { PageEquipment page new Page(pageNum, pageSize); LambdaQueryWrapperEquipment wrapper new LambdaQueryWrapper(); wrapper.like(StringUtils.hasText(query.getName()), Equipment::getName, query.getName()) .eq(query.getTypeId() ! null, Equipment::getTypeId, query.getTypeId()) .eq(query.getStatus() ! null, Equipment::getStatus, query.getStatus()) .orderByDesc(Equipment::getCreateTime); IPageEquipment equipmentPage this.page(page, wrapper); // 转换VO补充分类名称等冗余信息 return convertToVO(equipmentPage); }这里用到的like、eq方法第一个参数是boolean只有条件为true的时候才会拼接这个查询条件可以说是MyBatis Plus最实用、最常用的特性。写出来的代码非常简洁也不会出现字符串拼接SQL导致注入的问题。状态控制我建议在实体类里定义常量或者在代码里用一个枚举类不要把数字魔法值散落在各处public class EquipmentStatus { public static final int AVAILABLE 0; public static final int BORROWED 1; public static final int REPAIRING 2; public static final int SCRAPPED 3; }借用审批通过时的代码逻辑大概是这样的更新借用记录状态为已通过同时更新器材状态为已借出。为了保证这两步要么都成功要么都失败必须加事务注解Transactional(rollbackFor Exception.class) public void approveBorrow(Long recordId) { BorrowRecord record borrowRecordService.getById(recordId); // 校验记录存在、状态为待审批 record.setStatus(BorrowStatus.APPROVED); borrowRecordService.updateById(record); Equipment equipment equipmentService.getById(record.getEquipmentId()); equipment.setStatus(EquipmentStatus.BORROWED); equipmentService.updateById(equipment); }Transactional是Spring里很容易被忽视但非常重要的注解。如果忘了加事务一旦更新器材状态时报错了借用记录状态已经改了数据就处于不一致的状态后面排查起来会很头疼。4. 开发中的难点与排查记录4.1 Spring Boot版本与配置类问题我踩的第一个坑是Spring Boot版本导致的问题。有一段时间我用的是Spring Boot 3.2.0版本结果项目中引入的一些依赖比如某些数据库连接池、验证框架版本不兼容启动时直接报ClassNotFoundException。后来把所有依赖统一调整到Spring Boot 2.7.18版本用spring-boot-dependencies的BOM统一管理版本问题才彻底解决。这里也提醒一下依赖版本尽量不要自己单独指定交给Spring Boot BOM去管理除非你知道自己在做什么。第二个坑是静态资源404。前端静态文件放在了resources/static目录下但访问的时候一直报404后来发现是因为我实现了一个WebMvcConfigurer重写了addResourceHandlers方法把默认的静态资源映射覆盖掉了。解决办法很简单在重写的方法里同时加一行默认映射或者不要重写直接用默认路径。第三个坑是跨域问题。前端Vue跑在localhost:5173后端接口在localhost:8080前端请求接口时浏览器直接报跨域错误。最简单粗暴的办法是在启动类上加CrossOrigin但这样所有接口都放开跨域了不太优雅。推荐用配置类统一处理Configuration public class CorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOriginPattern(*); config.addAllowedMethod(*); config.addAllowedHeader(*); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/api/**, config); return new CorsFilter(source); } }4.2 MyBatis Plus分页与联表查询分页功能是后台管理系统的高频需求MyBatis Plus用分页插件实现分页很简单但有一个很隐蔽的坑如果忘记注入分页插件调用page()方法时不会报错但返回的数据是全部数据分页参数不生效很多人遇到这个bug都会一脸懵。配置插件的方法如下Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }第二个坑是关于联表查询分页的。器材列表页需要显示分类名称而分类名称在equipment_type表里equipment表里只有type_id。在分页查询时需要join分类表把分类名称带出来。如果直接用MyBatis Plus的page()方法只能查单表这时候我用的是自定义SQLXML文件select idselectEquipmentPageWithType resultTypecom.example.vo.EquipmentVO SELECT e.*, t.type_name FROM equipment e LEFT JOIN equipment_type t ON e.type_id t.id WHERE e.deleted 0 if testquery.name ! null and query.name ! AND e.name LIKE CONCAT(%, #{query.name}, %) /if if testquery.typeId ! null AND e.type_id #{query.typeId} /if ORDER BY e.create_time DESC /select注意这个地方在写自定义查询的时候也要手动判断deleted 0因为MyBatis Plus的逻辑删除注解只对BaseMapper内置方法生效自己写的SQL不会自动追加逻辑删除条件。这个细节非常容易忽略但忽略之后就会出现已经被删除的器材还能被查出来的诡异bug。4.3 登录拦截与角色权限实现登录和权限控制是毕业设计项目里老师非常爱问的一个点。我的实现方式是登录成功后把用户信息存到Session或者用JWT生成Token返回给前端。考虑到这个项目不涉及分布式部署Session方案完全够用代码量也更少。后端用一个拦截器统一处理登录校验Component public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session request.getSession(); Object user session.getAttribute(loginUser); if (user null) { response.setContentType(application/json;charsetUTF-8); response.getWriter().write({\code\:401,\msg\:\未登录或会话已过期\}); return false; } User currentUser (User) user; // 管理端接口单独校验角色 String uri request.getRequestURI(); if (uri.startsWith(/api/admin/) !ADMIN.equals(currentUser.getRole())) { response.getWriter().write({\code\:403,\msg\:\无权限访问\}); return false; } return true; } }然后注册这个拦截器放行登录接口和静态资源Configuration public class WebConfig implements WebMvcConfigurer { Resource private LoginInterceptor loginInterceptor; Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor) .addPathPatterns(/api/**) .excludePathPatterns(/api/user/login, /api/user/register); } }这样在Controller里就不用每个接口都去判断用户是否登录、有没有权限拦截器统一处理代码干净很多。密码存储上不要用MD5MD5在现在的算力下跟明文没有太大区别我用的是BCrypt加密Spring Security里自带BCryptPasswordEncoder单独抽出来用就行。4.4 高频报错速查表做这个项目的时候我在几个技术交流群里也帮人看过问题把最常见的一些报错整理出来给正在做类似项目的同学一个排查思路报错现象可能原因解决办法启动报Failed to configure a DataSource数据源配置错了或者没有引入JDBC驱动检查application.yml里的URL、用户名、密码确认mysql-connector-java依赖存在接口返回Whitelabel Error Page请求路径不对或者Controller没被扫描到检查RequestMapping路径确认启动类在Controller的父包下查询列表长期不返回SQL写在XML里但Mapper方法参数没加Param给多参数方法加Param(query)注解分页一直是全量数据没注入分页插件配置MybatisPlusInterceptor并添加分页拦截器前端请求跨域前后端端口不同配置CORS过滤器Invalid bound statement (not found)Mapper接口和XML文件没有绑定检查XML的namespace和Mapper接口全限定名是否一致确认mybatis.mapper-locations配置路径正确这些报错基本都是初学者必踩的类型很多是配置层面一两个单词写错导致的。遇到问题的时候先看控制台完整报错信息不要只看错误的第一行也不要直接复制错误内容去问人先自己读一遍往往就能定位到问题所在。5. 给毕业设计同学的落地建议5.1 从0到1的开发路线如果你打算用这个题目做毕业设计我给一个可以参考的开发顺序按照这个顺序走会顺利很多第一步花半天时间把数据库表建好把表之间的关系理清楚。数据库设计想清楚了后面开发就是体力活。第二步实现最基础的用户注册登录、器材的增删改查。先让系统跑起来有一个能操作的管理界面增加信心。第三步实现借用流程。这个环节涉及状态流转和事务是项目的核心亮点需要仔细设计。第四步补上报修、统计报表、数据可视化这些功能。统计报表可以用ECharts后端提供聚合统计接口前端画柱状图、饼图视觉效果很好答辩演示的时候非常加分。第五步完善细节包括权限校验、参数校验、异常处理、头像上传等。这些看似不起眼但能让系统看起来专业很多。前端的页面不用全部从零写。Vue的admin管理模板很多比如vue-element-admin、RuoYi框架可以先下载一个精简版把登录页面和布局框架跑起来然后按照自己的需求改造页面。直接使用开源模板不算丢人关键是你要能说清楚每个页面调用了哪个接口、数据是怎么流转的。5.2 演示流程与答辩准备答辩演示的时候建议提前准备好一条完整的演示路径不要现场临时点来点去。我的习惯是这样的先以管理员身份登录展示器材列表和器材分类然后切到普通用户账号演示提交一个借用申请再切回管理员账号演示审批通过再回到用户账号模拟归还最后展示报表页面用图表说明哪个器材借得最多哪个时间段借用最频繁。这一套流程走下来系统的主要功能基本全覆盖了大概需要五分钟节奏刚好。关于答辩时被老师提问我把高频问题提前整理了一下为什么选择Spring Boot而不是SSM答Spring Boot简化了配置和部署内嵌Tomcat依赖管理更统一适合快速开发Web应用。用户借用的并发问题怎么解决比如两个人同时申请同一件器材。答审批环节有状态校验器材被借走后状态置为已借出并行的申请会被拦截极端情况下可以使用数据库行锁或者乐观锁。为什么用逻辑删除答保留历史数据方便后续追溯借用记录和报修记录避免误删后无法恢复。报表数据是从哪来的答后端SQL做聚合统计用GROUP BY按分类和时间维度汇总前端ECharts渲染。这些问题其实不难但前提是你确实是自己把项目写完的。如果代码是找人代写的随便问一个你的器材状态在哪个字段有哪些值就能暴露出来。5.3 从项目到论文的整理思路论文框架可以直接跟着项目结构走背景与意义介绍校园器材管理的现状说明为什么要做线上化系统。关键技术讲Spring Boot、MyBatis Plus、Vue、MySQL每样都简单介绍核心特性。需求分析把前面讲的功能模块转化成用例图、用例描述。系统设计画系统架构图、功能结构图、数据库ER图。系统实现按模块贴核心代码配上运行截图代码不要全贴选关键片段即可。系统测试写功能测试和部分性能测试用例说明测试方法和结果。论文截图建议提前整理好。每个功能页面都要有截图尽量加上红框标注关键操作按钮这样答辩老师看论文的时候不费劲。目录结构、数据库表结构这些内容放附录不要占正文字数。另外做这个项目的时候源码和文档建议用Git管理起来每天提交一次写论文的时候可以直接翻看Git提交记录把开发过程中的里程碑节点写进论文的时间计划中显得项目管理能力也到位了。最后分享一点个人体会做这类Spring Boot管理系统项目其实技术难度并不算高真正考验人的是对业务流程的理解和细节的处理能力。我在开发这个器材管理系统的过程中感受最深的一点是不要急着写代码先把业务梳理清楚。比如借用申请提交之后器材是否立即锁定、审批拒绝之后申请记录的操作权限、器材损坏是用户主动报修还是管理员登记这些问题在动手前如果都想明白了后面编码的效率会高很多。另外如果想给项目加分可以在现有功能的基础上做以下几个扩展方向给器材生成二维码用户扫码就能查看器材信息和借用状态加入消息通知借用审批通过之后给用户发提醒增加大屏展示页面用于演示环境下的数据可视化。这些方向都能让系统看起来更完整、更有亮点也方便在论文里增加创新点。这套系统的代码结构、数据库设计、核心接口都按标准Maven工程整理过了目录结构清晰Controller、Service、Mapper分层明确可以直接作为毕业设计的基础框架使用。如果你正在准备类似的选题希望这份经验能帮你少走一些弯路。
返回列表