SpringBoot Vue 做高校固定资产管理系统的毕设这套选型在 Java Web 方向里确实是主流中的主流。它不是那种随便拼接出来的 CRUD 四不像而是把 SpringBoot 的后端工程化、Vue 的前端组件化、MySQL 的数据建模、接口文档的规范整条链路都打通了。这篇就把整个系统的拆解思路、数据库设计、后端关键实现、前端页面逻辑、还有部署和答辩时能讲的点一次性说透。1. 项目整体设计与方案拆解1.1 为什么固定资产管理是合适的毕设选题先聊聊选题。很多同学毕设选题要么太玩具比如图书借阅管理系统要么太虚比如什么智能推荐引擎做出来不像一个能落地的业务系统。固定资产管理属于典型的信息管理系统它有一个真实的业务闭环不是只写一张表的增删改查。高校资产管理的场景特别清晰学校有大量设备从教学楼投影到实验室的仪器从行政办公电脑到教学用的服务器存在资产录入、领用、归还、调拨、维修、报废、盘点这一整条生命周期。这些流程天然带有状态流转和权限区分管理员能做什么、普通用户能做什么这都是有业务含义的。这个选题对毕设来说有几个实际的好处。第一业务复杂度适中不需要算法不需要高并发核心逻辑清晰可讲第二涉及多表关联、事务、权限、报表统计评阅老师和答辩现场有东西可问第三项目结构和企业里真实的后台管理系统非常接近讲出去也不虚。1.2 SpringBoot Vue 前后端分离的选型理由技术选型这块为什么这个项目选择了 SpringBoot 加 Vue而不是传统的 JSP、Servlet也不是现在特别重的微服务。SpringBoot 解决的是后端开发的效率问题。它内嵌 Tomcat不用单独部署容器自动配置省掉大量 XML 配置配合 MyBatis Plus 操作数据库一个后端模块的开发速度比传统 SSM 快很多。这一点放在毕设场景尤其重要因为你的时间有限核心应该放在业务逻辑上而不是配置环境。Vue 解决的是前端页面开发的体验问题。传统 JSP 页面是后端渲染前后端代码混在一起改个样式都要重启很痛苦。Vue 的组件化思想把页面拆成一个个组件资产管理页面、表单弹窗、统计图表各自独立数据通过接口请求获取页面刷新局部更新不用整页跳转。前后端分离的核心价值在于后端只需要提供 RESTful API前端通过 Axios 发起请求拿到 JSON 数据再渲染。分工清晰调试也方便后端用 Postman前端用浏览器各自测各自的。1.3 功能模块与角色权限的全局划分整个系统按角色可以拆成三类系统管理员、部门资产管理员、普通教职工。系统管理员负责全局的资产分类维护、部门管理、用户账户管理、系统总览统计以及所有资产操作的审批和审计。部门资产管理员负责本部门的资产入库登记、日常使用的领用审批、调拨申请、维修上报跟进还有部门内的资产台账管理。普通教职工能查资产库、提交领用申请、报修、归还全程只能操作自己的申请单看不到其他人的流转记录。从功能模块上看系统拆成几大块首页工作台展示资产总数、部门资产统计、本月新增、待处理申请等卡片数据资产管理资产台账列表、资产入库登记、领用、归还、调拨、维修、报废全流程资产分类多级分类维护比如教学设备、办公设备、图书资料、后勤设施部门管理院系与行政部门的组织结构维护用户管理账号的增删改查、角色分配、密码重置统计报表按分类、按部门、按状态的图表统计与列表导出记录查询领用记录、维修记录、调拨记录、操作日志的追溯查询这个划分兼顾了业务逻辑的完整性和开发的可行性前端的每个菜单都能对应一套独立的 API 接口不会出现功能边界模糊、代码到处穿插的问题。2. 数据库设计SQL 脚本背后的核心表结构2.1 核心业务表的划分与关系梳理这套系统的数据库设计是整个项目的基石。评阅老师最常问的第一个问题就是“你的数据库表是怎么设计的”如果表之间的关系讲不清楚后面很难圆回来。我建议按业务域把表拆成这样几组基础信息组sys_user用户表user_id、username、password、real_name、dept_id、role_id、status、create_timesys_role角色表role_id、role_name、role_key、remarksys_dept部门表dept_id、dept_name、parent_id、leader、phoneasset_category资产分类表category_id、category_name、parent_id、code资产业务组asset_info资产信息表asset_id、asset_code、asset_name、category_id、dept_id、specification、price、purchase_date、warranty_date、status、responsible、location、remarkasset_borrow领用归还表borrow_id、asset_id、user_id、dept_id、borrow_time、return_time、status、remarkasset_allocate调拨表allocate_id、asset_id、from_dept_id、to_dept_id、allocate_time、operator_id、statusasset_repair维修表repair_id、asset_id、report_user_id、repair_reason、repair_time、finish_time、cost、statusasset_scrap报废表scrap_id、asset_id、apply_user_id、scrap_reason、scrap_time、approval_status辅助功能组asset_stock_record入库记录表stock_id、asset_id、dept_id、source、stock_time、operator_id、quantitysys_log操作日志表log_id、user_id、method、params、create_timesys_menu、sys_role_menu菜单权限表如果做动态路由和按钮级权限这两张表是必备的。表与表之间的关联主要集中在asset_info上。资产信息表的dept_id关联部门表表示当前存放部门category_id关联分类表表示资产类型user_id关联用户表表示当前领用人。领用、调拨、维修、报废表都是围绕资产 ID 做流水记录。2.2 资产编码、状态字段与逻辑删除的细节设计资产编码的设计很多人会忽略但它恰恰是最容易出彩的地方。可以采用三段式编码分类编码 年份 四位流水号。比如SB-2024-0012SB 是实验设备分类的编码2024 是入库年份0012 是当年该分类下的第 12 条资产。这种编码规则的好处是看到编码就知道资产是什么类别什么时候进来的。数据库层面给asset_code建唯一索引避免重复录入。代码层生成编码的逻辑是先根据分类编码和年份查出当前最大流水号加一后补零。资产状态字段建议用 int 类型存0 表示在库1 表示已领用2 表示维修中3 表示已调拨4 表示已报废。用 int 的好处是查询效率高、排序方便前端再通过数据字典翻译成中文。对外展示不要直接输出 0、1、2前端用 statusMap 映射成“在库”“已领用”“维修中”等文本。逻辑删除一定也要做。资产、部门、分类这些核心表不要物理删除因为资产一旦删除历史领用记录、维修记录就全断链了。在表里加一个deleted字段用 0 和 1 标记。MyBatis Plus 里配好TableLogic注解查询的时候自动带上过滤条件删除操作自动转成 update。2.3 索引设计与字段类型的实际操作建议索引是固定资产管理系统里很现实的问题。资产表可能有一两万条数据不做索引资产编码模糊查询、部门统计、状态筛选统统会变慢。一般加索引的规则是查询条件里频繁出现的字段建普通索引。asset_info表给asset_code建唯一索引给dept_id、category_id、status分别建普通索引。流水表asset_borrow给asset_id、user_id建索引asset_repair给asset_id、status建索引。字段类型方面有几个易踩的坑。价格字段不要用 float 和 double精度会出问题用decimal(10,2)。时间字段统一用datetime别混用timestamp和date前端的日期控件直接传字符串数据库负责存储。金额和流水号相关的字段SQL 脚本里要写清楚默认值和备注你自己后面看也要让老师能看懂。导入 SQL 脚本的时候千万注意字符集文件开头加上SET NAMES utf8mb4;数据库建库也要指定utf8mb4否则中文乱码问题会在你演示的时候突然冒出来非常尴尬。3. 后端核心实现SpringBoot 项目的关键代码逻辑3.1 项目分层与统一返回格式后端工程的分层尽量标准controller、service、mapper、entity、dto、vo、config、common。controller只做参数接收和返回service写业务逻辑mapper负责数据库操作接口entity对应数据库表字段dto用于接收前端参数vo用于返回给前端的数据结构。为什么不直接返回 entity因为前端要的数据格式和后端表结构不一定一致领用记录需要连表查出用户姓名和部门名称直接在 entity 上塞多余字段很乱用 vo 封装一次干净又安全。统一返回结果集是必备的。后端所有接口都返回一个ResultT对象包含 code、message、data 三个字段。成功返回 200业务异常返回 500未登录返回 401无权限返回 403。前端 Axios 响应拦截器只需要判断 code 就能统一做提示不用每个接口单独写错误处理。public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(操作成功); result.setData(data); return result; } public static T ResultT error(String message) { ResultT result new Result(); result.setCode(500); result.setMessage(message); return result; } }配合RestControllerAdvice做全局异常处理。业务异常用自定义的BusinessException抛出参数异常用Valid注解校验后统一拦截。这样 controller 层代码非常干净大部分都不需要 try-catch这是一个很重要的工程习惯。3.2 JWT 登录认证与权限控制的实现思路登录认证用的是 JWT。用户输入用户名密码后端校验通过后签发一个 tokentoken 里包含 userId 和 username然后前端每次请求在 header 里带上 Authorization。后端用一个拦截器或者 Spring Security 过滤器来校验 token。如果不想引入 Spring Security 那么重的东西可以直接用拦截器实现自己写一个JwtInterceptor实现HandlerInterceptor接口在preHandle里从请求头取 token解析出来把 userId 和 username 放到 ThreadLocal然后放行。同时指定拦截路径登录接口、校验验证码接口不需要拦截其他接口全部拦截。权限控制这块建议做一个简单的角色判断。后端在需要权限的接口上用自定义注解RequirePermission(asset:add)标记拦截器里解析当前用户的角色再判断是否有对应权限码。这样不用引入完整 RBAC 框架也能把前端菜单权限和后端接口权限串起来。密码存的是 BCrypt 加密后的密文数据库里不要出现明文密码。初始化的时候在 SQL 脚本里放入一个预先加密好的管理员密码或者干脆写一段 CommandLineRunner 在项目启动时检查并初始化管理员账户这样你重新导入数据库后不用手动去改密码。3.3 资产领用、归还、调拨、维修的业务状态流转资产全流程是最能体现业务逻辑的部分也是答辩时最容易说清楚的部分。简单梳理一下每个流程的状态机。领用流程用户提交领用申请资产状态从在库变成待领用管理员审批通过后状态变成已领用同时更新asset_info的责任人和存放部门往asset_borrow插一条记录归还时状态从不占用变回在库更新归还时间。这个流程在代码里要注意事务。领用审批时既要在asset_borrow表插入记录又要更新asset_info的资产状态这两步必须放在同一个事务里否则会出现领用记录已经生成但资产状态没变的情况。调拨流程调拨的本质是资产的部门归属变更。从 A 部门调到 B 部门提交调拨申请时记录 from_dept_id 和 to_dept_id审批通过后回写资产表。前端展示时需要显示原部门和新部门所以调拨记录表不能只保存调拨后的部门。维修流程资产报修后状态切到维修中维修表记录报修原因和费用维修完成状态恢复到之前的可用状态。这里需要单独设计一个状态位比如repairStatus来区分修复和报废因为如果维修费大于资产原值业务上会选择直接报废。这个状态流转的逻辑建议在每个状态变更接口里先做前置校验比如资产已报废就不能再领用已领用的资产不能重复领用。校验逻辑写在 service 层最前面不符合条件就直接抛异常不要等脏数据落库后再查。3.4 分页查询、条件筛选与 Excel 导入导出列表页面的分页查询使用 MyBatis Plus 的Page对象。控制器接收当前页和每页条数service 层用LambdaQueryWrapper构造条件比如资产名称的模糊查询、状态精确匹配、部门和时间范围筛选最后返回IPage对象。PageAssetVo page new Page(pageNum, pageSize); LambdaQueryWrapperAssetInfo wrapper new LambdaQueryWrapper(); wrapper.like(StringUtils.isNotBlank(name), AssetInfo::getAssetName, name) .eq(status ! null, AssetInfo::getStatus, status) .eq(deptId ! null, AssetInfo::getDeptId, deptId) .between(startDate ! null endDate ! null, AssetInfo::getPurchaseDate, startDate, endDate) .orderByDesc(AssetInfo::getCreateTime);注意一个坑分页插件不生效的常见原因是没配置分页拦截器。只有在 MyBatis Plus 配置类里添加PaginationInnerInterceptor分页查询才会自动拼接 limit 语句否则查出来的是全量数据前端翻页永远是第一页。Excel 导入导出用的 EasyExcel。导出就是查询列表数据写入 Excel 后响应给前端下载导入就是接收 MultipartFile一行行解析后校验字段再插入数据库。导入的坑在于Excel 里用户填的部门名称和数据库里的不一定一致建议先根据部门名称批量查出部门 ID做一次映射再入库。导出的坑在于文件名编码用URLEncoder.encode(fileName, UTF-8)否则浏览器下载时中文文件名会乱码。4. 前端实现Vue 项目从页面到接口的完整链路4.1 前端工程结构与路由拆分前端工程用 Vue CLI 或 Vite 初始化结构上分成api、router、store、views、components、utils几个目录。api目录下按模块拆分接口比如asset.js、user.js、dashboard.js每个文件导出对应模块的请求函数。views目录下按页面拆分资产台账页面、领用管理、维修管理、统计报表一个业务模块一个文件夹。路由拆分的建议是登录页独立主布局里挂载各个业务页面。同时要在路由beforeEach里做登录态判断没有 token 一律跳到登录页。如果一个页面需要管理员权限可以在路由 meta 里配置 roles然后做跳转拦截。如果做按钮级权限前端拿到后端返回的权限码列表后存到 store 里页面上用自定义指令v-permissionasset:add判断当前用户是否有权显示按钮。没有权限码的按钮直接不渲染这样至少从界面上规避了普通用户看到管理员按钮的尴尬。4.2 资产管理页面的表格、查询表单与弹窗交互资产管理页面的核心是资产台账列表。布局上上半部分是一个查询表单包含资产名称、资产状态、所属部门、入库日期范围四个查询条件下半部分是一张表格展示资产编码、资产名称、分类、部门、价格、状态、购置日期和操作按钮。表格用 el-table 实现核心字段用el-table-column渲染。状态列不要直接展示 0 和 1用formatter函数映射成 tag 标签比如在库是绿色标签维修中是橙色标签报废是灰色标签。操作列根据状态显示不同按钮在库资产显示领用和调拨已领用显示归还维修中显示维修完成这个细节会让页面交互感强很多。新增和编辑资产建议用弹窗里的 el-form 实现表单校验规则用 rules 配置。注意分类选择要用级联选择器因为分类表是多级的部门选择也要配成树形结构否则几十个部门放在下拉框里不好选。前端的一个小经验是表格数据请求和搜索条件联动时直接用一个对象作为参数传给后端。每次搜索条件变化时页码重置为 1然后重新请求数据。分页组件el-pagination的 current-page 和 page-size 要绑定固定变量否则点击翻页后查询条件丢失。4.3 Axios 封装与数据可视化统计Axios 封装在utils/request.js里。请求拦截器从 localStorage 或 store 里取 token塞进请求头响应拦截器统一处理返回的 code。code 是 200 就直接返回 data401 表示 token 过期跳转登录页并清除本地 token其他错误码统一弹 ElMessage 提示后端每个接口都不用写错误处理逻辑。service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization token } return config }) service.interceptors.response.use( response { const res response.data if (res.code 200) { return res.data } if (res.code 401) { localStorage.removeItem(token) router.push(/login) } ElMessage.error(res.message) return Promise.reject(new Error(res.message)) }, error { ElMessage.error(网络请求异常) return Promise.reject(error) } )统计报表页面用 ECharts 做可视化。常见的有三个图表按资产分类的饼图、按部门资产数量的柱状图、按月份新增资产的折线图。数据来源是后端统计接口返回 id 和 value 的列表前端直接转换成 ECharts 的 data 格式。这里有个后端优化的点统计接口可以一次性返回多个图表需要的数据封装成一个 DashboardVO前端一次请求全部拿到而不是发四个接口。系统首页加载卡顿的问题多半是统计接口查得太慢统计时要保证 group by 的字段有索引日期按月份统计可以用DATE_FORMAT(purchase_date, %Y-%m)来做分组。5. 接口文档与项目部署 上线前必须做好的两件事5.1 Swagger 接口文档的配置与用法接口文档是这套项目源码里很重要的一环也是很多人容易忽略掉的内容。用 Swagger 生成文档在 SpringBoot 里加依赖然后用注解描述接口信息。后端每个 controller 加Api(tags 资产管理接口)每个接口方法加ApiOperation(资产列表查询)参数对象用ApiModelProperty(资产名称)描述字段。配置完成后访问http://localhost:8080/swagger-ui/index.html就能看到全部接口。接口文档的价值不只是给答辩老师看你自己调试前端的时候也很重要。前端对接接口时有歧义直接查文档确认字段名和类型不用去翻后端代码。好的文档习惯是字段命名统一用驼峰时间参数格式统一是yyyy-MM-dd HH:mm:ss分页参数固定用 pageNum 和 pageSize这样前后端联调效率会高很多。5.2 本地启动数据库导入、后端配置、前端环境本地把项目跑起来的完整过程值得写一遍因为很多同学第一次拿到源码卡在环境配置上很久。第一步准备好 JDK、Maven、Node.js 和 MySQL。建议版本组合是 JDK 8 配 SpringBoot 2.xNode.js 14 以上配 Vue 2 或 Vue 3MySQL 5.7 或 8.0 都可以。第二步导入数据库。直接用 Navicat 执行项目里的 SQL 脚本注意先建库再导入数据库名称和脚本里保持一致比如asset_management。第三步修改后端配置文件application.yml。数据库地址、用户名、密码必须改成自己本地的另外检查一下 redis 配置如果没有安装 Redis就要把验证码存储相关的配置改成本地 Map 形式或者干脆先跳过验证码功能。第四步启动后端。在项目根目录执行mvn spring-boot:run看到控制台打印启动成功就说明没问题。如果端口被占用修改配置里的 server.port或者看看是不是上一次启动的进程没关掉。第五步启动前端。在frontend目录执行npm install安装依赖后执行npm run dev。前端开发服务器的默认端口是 8080如果和后端冲突就改一下vue.config.js里的 port同时配好 devServer 的 proxy 把/api转发到后端端口。5.3 前后端联调与打包部署方案本地联调时一个常见问题是跨域。最省事的处理是在前端vue.config.js里配置代理开发环境请求统一走/api前缀代理转发到后端服务这样浏览器看到的都是同源请求不需要后端加 CORS 配置。devServer: { port: 8081, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }如果后端也配了 CORS 允许跨域双端会同时存在跨域配置开发环境反而可能出现重复问题。建议只保留一端的跨域处理后端留一个全局 CORS 配置前端不要代理或者前端用代理后端不做跨域二选一即可。打包部署如果是用来答辩演示在本地跑开发环境完全够用。如果只是临时部署到服务器演示提供一个 Nginx 部署页面前端打包成 dist 目录放到 Nginx 的 html 目录反向代理/api到后端端口后端maven package打成 jar 包nohup java -jar启动。这个方案简单可靠答辩前能快速搭出环境。6. 常见问题与排查技巧实录6.1 数据库连接失败、时区与编码问题后端启动时最常见的报错是数据库连接失败要么是数据库没启动要么是密码错了要么是时区和编码问题。连接串的末尾一定要加上serverTimezoneAsia/Shanghai和characterEncodingutf8MySQL 8.x 的驱动用com.mysql.cj.jdbc.Driver新版驱动类名和旧版不一样配置错会直接报类找不到。中文乱码问题排查顺序先看数据库表字符集是不是 utf8mb4再看后端连接串是不是指定了编码最后看前端页面 meta 标签。一条条检查基本都能定位出来。6.2 启动报错端口占用与依赖版本冲突端口占用是最常见的启动失败原因报错信息里会有Port already in use。Windows 下用netstat -ano | findstr 8080查进程号然后taskkill /F /PID 进程号杀掉。Linux 下用lsof -i:8080查端口。依赖版本冲突主要出现在 SpringBoot、MyBatis Plus、MyBatis 之间的版本组合。SpringBoot 2.7.x 对 MyBatis Plus 的兼容性比较好如果用的是 3.5.x 版本找对应的 mybatis-plus-boot-starter。遇到启动报错找不到 SqlSessionFactory 之类的基本是版本不匹配直接统一降级或升级到官方文档推荐的组合。6.3 前端接口调不通与菜单权限返回 401 问题前端调后端接口调不通先把网络请求面板打开看 404 还是 401 还是 500。404 说明后端没有这个接口路径检查前端请求的 URL 和后端 controller 的映射是否一致特别是RequestMapping里的类级别路径漏掉前缀是最常见的。401 说明 token 没有带或者 token 过期检查请求拦截器是不是取错了 token 字段。500 说明后端代码运行时出了异常看后端控制台的堆栈日志大部分是空指针或者 SQL 语句报错。菜单权限返回 401 还有一个常见原因是后端拦截器放行了登录接口但没有放行前端首次加载时请求用户信息的接口。排查的时候把未登录时需要访问的接口全部加到白名单里。6.4 我的踩坑心得 拿到一套源码后应该怎么快速上手如果你拿到的是别人整理好的完整项目源码别急着直接启动。先花十五分钟把三个东西看清楚数据库脚本里的表结构、后端 controller 的接口列表、前端路由对应的页面。这三样看完整个系统的脉络就大概清楚了。然后启动起来之后不要只看页面好不好看重点走一遍核心流程新建资产、领用这个资产、再归还这个资产。每一步在数据库里对应哪条 update哪条 insert心里要有数。答辩的时候说得出这一套流程比背十个概念点都要有说服力。遇到暴错不要慌先看后端控制台日志再看前端网络请求的响应体。大部分毕设项目的问题都不是逻辑难题而是环境、路径、字段名对不上一层层拆开排查就好了。我个人在实际操作中的体会是固定资产管理系统这种选题核心不在于代码写得多么花哨而在于业务逻辑是否完整、数据流转是否闭环。把资产生命周期这条主线讲明白每一张表为什么存在每一个字段为什么需要每一个状态为什么这样流转你能把这三个问题回答得清楚流畅再加上一套完整的前后端代码这个毕设的质量就是稳的。