☰
SpringBoot+Vue+MySQL:建筑材料管理系统毕业设计全栈实战
2026/9/26 4:36:46 网站建设 项目流程

做毕业设计最怕的不是写不出代码,而是不知道系统的边界到底在哪里。这两年我陆续收到不少读计算机或软件工程专业的同学提问,十个人里有六个都在做同一个方向的题目:SpringBoot加Vue写一套管理系统。而《建筑材料管理系统》就是其中出镜率非常高的一道题。很多人开始以为这就是一套简单的增删改查,可真打开需求一看,入库、出库、供应商、库存预警、材料分类、项目领料、统计报表,每一样都要处理好。基于SpringBoot框架做后端、Vue做前端、MySQL存数据,再加上数据库脚本和毕业论文文档,这套方案完全可以覆盖一个计算机毕业设计的全部工作量。本文把从需求拆解、表结构设计、后端接口实现、前端页面联调,到毕业论文整理和常见踩坑点,完整梳理了一遍,适合正在准备毕设、计划把题目做成全栈项目的同学参考,也适合想掌握SpringBoot+Vue开发闭环的入门者当作实战案例来啃。

1. 题目拆解:建筑材料管理系统到底在管什么?

1.1 先想清楚角色和流程,再决定做多少页面

很多同学拿到题目会急着建工程,这是最容易踩的坑。建筑材料管理系统,听起来很像教材里跑通的“XX管理系统”,于是上来就是一个MaterialController,几个页面对应四个增删改查,做完就以为万事大吉。但把它放到真实场景里,材料不是一个简单字典,它从“进”到“存”再到“出”是有流转的:供应商送来材料,仓管员登记入库,库存增加;施工队或项目需要用料,仓管员登记出库,库存减少;当库存低于安全线,系统要提醒采购补货。整个过程还涉及价格、数量、供应商信息的追溯,如果不提前拆清楚,后面写代码时十有八九会把字段堆在同一张表里。

我建议把角色聚焦为三类:系统管理员负责用户和基础数据维护,仓管员负责材料、供应商和出入库操作,普通用户只做查询与领料申请。如果学校对用户管理要求不高,甚至可以简化成管理员和仓管员两种角色。核心模块放在五个地方:材料分类管理、供应商管理、入库管理、出库管理、库存查询与预警。项目领料这一块如果做进去,需要在出库单上增加“领用单位”或“项目名称”字段;如果不想扩展,出库单上的“领用人 + 领用项目”两个字段也能解决演示问题。

功能范围确定之后,再画一个简单的功能结构图,心里就会清楚每张页面放哪、每个接口服务谁。这一步花不了半小时,但能在后面省下好几个晚上的返工时间。尤其要跟指导老师确认清楚:要不要做项目维度统计、要不要做材料盘点、要不要做多级审批。这类扩展是最容易让答辩老师觉得“工作量不够”的要素,也是系统从“练习项目”走向“毕业设计”的分水岭。

1.2 为什么放弃JSP,选择SpringBoot+Vue这套组合

前几年很多校内课程还在用SpringMVC加JSP,页面和服务端混在一起,写起来省事,但答辩时不好讲,功能复杂以后维护也痛苦。现在企业里做管理系统,大多数是前后端分离的架构:后端用SpringBoot提供REST接口,前端用Vue框架通过Ajax调用接口渲染页面。两个工程可以独立开发、独立测试,也方便分工。如果你的项目组有两三个人,一个人写后端接口,一个人写页面,数据库脚本可以由另一个同学专门维护,效率会高很多。

SpringBoot在这里的核心价值是“简化配置”:内嵌Tomcat,不用单独部署Web容器;默认支持REST接口;配合MyBatis-Plus之后,连最简单的单表CRUD都可以少写大量SQL。Vue的核心价值是“组件化”:一个表格页面、一个弹窗表单、一个状态标签,都能拆成组件复用,页面代码比传统模板引擎清晰得多,也更容易体现出工作量。数据库用MySQL就够了,同时把SQL脚本导出发给组员和老师,保证环境一致。

当然这个选择也有代价:前后端联调需要处理跨域、接口约定、Token等额外问题。这些我会在后面的章节里逐个写出来。总体而言,SpringBoot+Vue+MySQL是目前毕业设计里性价比最高的全栈组合,既踩得到技术点,又不容易绕进底层坑里出不来。

1.3 功能边界:哪些能做,哪些先不做

一个常见的错误是需求发散。同学做到一半突然想加“材料二维码扫码入库”“微信小程序查看库存”“智能补货预测算法”,然后项目就不受控制了。毕业设计的核心不是功能越多越好,而是让每一个功能都能被讲清楚、能演示、能和数据库设计对上。我的建议是,所有扩展功能先问自己三个问题:数据从哪里来?要新增几张表?演示时是否能在两分钟内走到效果?答不上来就先不做。

真正可以低成本加入的扩展点有两个:一是库存预警,在材料表加一个safety_stock字段,库存查询时用当前库存和安全库存做比较即可;二是首页统计看板,统计今天入库多少、出库多少、低库存材料总数。这两个功能不复杂,却很能提升系统的完整度,也能让答辩PPT里的“亮点功能”多一页。

2. 数据库设计:一张库存表撑不起一个系统

2.1 核心表拆解与字段规划

数据库是这套系统最容易翻车的地方。很多同学会直接在material表里放一个stock字段,然后入库时update把它加一,出库时减一。单机跑demo没有任何问题,但论文里写不下去,“库存账与单据对应关系”也说不清。所以建议把库存和业务单据分离:库存表只是一个状态结果,真正可信的数据是每一张入库单、出库单。

基础资料有三张表:材料分类表material_category,材料表material,供应商表supplier。材料表至少要包含分类ID、材料编号、名称、规格型号、计量单位、参考单价、安全库存、状态。材料编号建议设计成唯一编码,例如JC-001这种业务编码,而不是直接用主键。供应商表则放供应商编码、名称、联系人、电话、地址。

业务单据用主从表结构,这样一张入库单可以记录多种材料,也更贴近仓库作业。入库单主表stock_in:单号、供应商ID、操作人、入库日期、总金额、备注;子表stock_in_item:主表ID、材料ID、数量、单价、小计金额。出库同理,分别为stock_out和stock_out_item,出库子表字段可以省去价格,但增加领用单位等信息。库存表inventory只保留material_id和quantity,material_id需要设为唯一索引,保证每种材料只有一条库存记录。

这种设计的意思是:所有库存变化都能从单据回溯,而不只是“内存里面一个数字加减”。很多老师第一眼就会看表结构,见了主从表基本能确认你认真做了数据库设计。可以说这是整套系统的地基。

2.2 关键约束与索引设计

建表时不要偷懒不加外键。虽然MyBatis和代码能保证数据一致,但在数据库层面加上外键约束,能防止直接SQL操作造成脏数据。至少要给子表的material_id、supplier_id等字段加上外键约束或逻辑外键索引。例如ALTER TABLE stock_in_item ADD INDEX idx_material (material_id);(如果使用了FK,则外键自动索引)。需要特别注意:如果使用MySQL默认的InnoDB存储引擎,删除被引用材料时会受外键约束限制,这反倒是一种保护。

时间字段统一使用datetime,金额字段使用decimal(10,2),数量字段使用decimal(12,3)而不是int,因为很多建材不是按整袋出库的,沙子水泥可能按吨、按立方米计量。状态字段用tinyint,比如0为停用、1为启用,不要用字符串。布尔值也尽量用tinyint(1),避免不同MySQL版本对boolean的兼容性差异。

建一张材料表可以参考下面的SQL核心片段:

CREATE TABLE `material` ( `id` bigint NOT NULL AUTO_INCREMENT, `category_id` bigint NOT NULL COMMENT '分类ID', `code` varchar(50) NOT NULL COMMENT '材料编码', `name` varchar(100) NOT NULL COMMENT '材料名称', `spec` varchar(100) DEFAULT NULL COMMENT '规格型号', `unit` varchar(20) DEFAULT NULL COMMENT '计量单位', `reference_price` decimal(10,2) DEFAULT NULL COMMENT '参考单价', `safety_stock` decimal(12,3) DEFAULT NULL COMMENT '安全库存', `status` tinyint DEFAULT '1' COMMENT '1启用,0停用', `create_time` datetime DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_code` (`code`), KEY `idx_category` (`category_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='材料信息表';

这里设置了唯一键uk_code,防止材料编码重复;idx_category用于分类筛选;字段注释写清楚,后面直接复制到论文数据库设计章节也很方便。

最后一个细节:如果用了MyBatis-Plus,实体类的字段名与数据库列名的驼峰映射要打开map-underscore-to-camel-case: true。SpringBoot的properties中通常是mybatis-plus.configuration.map-underscore-to-camel-case=true。否则你会遇到create_time查出来是null这种诡异问题。

2.3 初始化数据与数据库脚本准备

数据库脚本不是简单把建表语句粘出来就行。一份合格的毕业设计SQL脚本,应该包含创建数据库、建表、插入基础数据三个部分。基础数据至少要有:管理员账号(密码加密后存入),两三个供应商,材料分类为沙子、水泥、钢材等,加上对应的材料和库存数据。这样前端页面一启动就有内容可以展示,不至于登录后空空如也,演示效果大打折扣。

如果用手工Excel整理再导入,要注意字符集问题,建议统一utf8mb4。另外给脚本加上注释,表明每张表的作用,文件头写明“导入前请先执行source create.sql”。这样指导老师在本地几分钟就能把环境跑起来,后面答辩演示会非常顺畅。

3. SpringBoot后端:把业务写清楚,别只做CRUD

3.1 工程分层与统一接口返回

后端工程建议按controller、service、service.impl、mapper、entity、dto、config、common来分层。entity对应数据库表,dto用来接收页面传递的参数,比如分页参数、入库单DTO,不要一股脑把前端参数全塞进实体类。controller只做参数接收和结果转发,不写业务。service处理业务规则,mapper只做数据库操作。

所有接口返回值建议统一封装成一个泛型Result类,前端收到后只需要判断code就可以知道业务成功还是失败。这里给出一份最精简的写法:

public class Result<T> { private Integer code; private String msg; private T data; public static <T> Result<T> success(T data) { Result<T> r = new Result<>(); r.setCode(200); r.setMsg("success"); r.setData(data); return r; } public static <T> Result<T> error(String msg) { Result<T> r = new Result<>(); r.setCode(500); r.setMsg(msg); return r; } // getter/setter 省略 }

这样做的好处是前端axios拦截器只需要判断code,不管每个接口返回结构,代码整洁很多。再配合@RestControllerAdvice全局异常处理,业务方法里只要抛出BusinessException,前端就能拿到一个统一的错误提示,不用每个controller写try-catch。

3.2 登录认证与密码加密

管理系统不该裸奔,登录功能是必须的。毕业设计做Token登录已经足够:用户登录成功后,后端根据用户信息生成一个Token,前端保存到localStorage并放在请求头里,后端用拦截器校验每个需要登录的接口。Token可以用JWT,也可以用普通UUID加Redis。如果不想引入Redis,用JWT最省事,密钥写在配置里即可。

密码存储不能明文,建议用Spring Security自带的BCryptPasswordEncoder,或者用Hutool的BCrypt工具类。网上很多教程用MD5加盐也凑合,但答辩时老师说BCrypt更专业。把这个细节放进论文“系统安全性”那一节,有加分效果。登录接口示例思路:

@Service public class UserServiceImpl implements UserService { @Override public String login(String username, String password) { User user = userMapper.selectOne(new LambdaQueryWrapper<User>() .eq(User::getUsername, username)); if (user == null || !BCrypt.checkpw(password, user.getPassword())) { throw new BusinessException("用户名或密码错误"); } // 生成JWT return Jwts.builder() .setSubject(user.getUsername()) .setExpiration(new Date(System.currentTimeMillis() + 24 * 3600 * 1000)) .signWith(SignatureAlgorithm.HS256, secretKey) .compact(); } }

这里secretKey通常放到application.yml里,用环境变量覆盖更好。答辩时问“Token过期怎么办”,只要说响应拦截器统一处理401跳回登录页,并把有效期设为24小时即可,这个回答足够完整。

3.3 入库与出库:用事务保证库存一致性

入库的核心逻辑是:把单子写进stock_in主表,同时把明细写进stock_in_item,最后把数量合并到inventory表。每张单子可能对应多种材料,所以入库接口通常接收一个主表DTO和子表List。Service中先插入主表拿到主键ID,再循环插入子表,最后更新库存。三个动作必须在一个事务里完成,否则一旦中间报错,就会出现“单据写入了,库存没加”的数据不一致问题。

出库逻辑相反,但机会点更多:先查当前库存,如果小于出库数量,直接抛异常“库存不足”;如果够用,则写入出库单和明细,接着扣减inventory表的数量。并发环境下还要考虑两个请求同时出库同一材料的情况,这里给一个常见的悲观锁写法:

Inventory inv = inventoryMapper.selectForUpdate(materialId); if (inv == null || inv.getQuantity().compareTo(demandQty) < 0) { throw new BusinessException("库存不足"); } inv.setQuantity(inv.getQuantity().subtract(demandQty)); inventoryMapper.updateById(inv);

selectForUpdate是加了for update的SELECT语句,会锁住这一行,直到事务提交,其他事务必须等待,从而避免超卖。多个人同时操作时,这个写法比“查出来判断再直接update”要安全得多。别看这个点很小,写进论文“技术难点”部分非常加分。

3.4 查询、分页与多条件筛选

材料列表、库存列表这类页面几乎都有分页。MyBatis-Plus配合分页插件可以省掉大量手写LIMIT的活。配置一个MybatisPlusInterceptor加入PaginationInnerInterceptor,然后在Service里写:

Page<Material> page = materialMapper.selectPage(new Page<>(current, size), new LambdaQueryWrapper<Material>() .like(StringUtils.hasText(name), Material::getName, name) .eq(categoryId != null, Material::getCategoryId, categoryId) .orderByDesc(Material::getId));

这样前端只需要传current、size、name、categoryId,后端返回总数量和列表数据即可。要注意like条件需要判断参数不为空,不然会出现收到空条件时返回空列表的尴尬。对于库存预警接口,其实就是lessThan查询安全库存与当前库存的差值,直接在inventory和material表关联查询即可。

4. Vue前端:从空页面到完整交互

4.1 项目初始化和目录组织

前端我建议用Vue3配合Vite,也可以按学校要求用Vue2加Vue CLI,核心逻辑差不多。如果还不太熟悉,用Vue2更稳妥,资料多、踩坑少;Vue3也是目前主流,组件写法更简洁。创建工程命令不复杂:

npm create vite@latest bms-web -- --template vue cd bms-web npm install npm install axios vue-router pinia element-plus

src目录下可以分:api存放axios封装和接口定义;router存放路由表;store存放Pinia的登录状态;views放页面;components放公共组件;utils放本地存储和鉴权工具。前端页面数量控制在八到十个左右:登录页、系统布局、材料分类、材料列表、供应商管理、入库管理、出库管理、库存查询、库存预警。如果还想加一个简单的看板页或首页统计,难度也不大。

4.2 路由守卫与Token处理

页面是不能直接打开的,登录后才能进入系统。在router.beforeEach里判断localStorage里有没有token,没有就重定向到/login。axios封装时加请求拦截器:

service.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) config.headers.Authorization = `Bearer ${token}`; return config; });

响应拦截器统一处理code不为200的情况,弹出错误消息并跳登录。这样每个页面的接口调用都只用关心业务数据,代码干净很多,答辩时展示这一层封装会让老师觉得你有工程意识。

4.3 材料进销存页面的实现要点

材料列表页是典型的“搜索+表格+分页+弹窗”,重点不是把功能堆出来,而是要把状态管理清楚。表格数据用ref维护,查询参数用一个reactive对象,调用接口后重新加载。Element Plus的el-table自带多选、排序、空状态,入库弹窗里选择材料时,最好联动展示该材料当前的可用库存,这样出库时用户就知道不能超出多少。出库表单提交前做一次前端校验,比如出库数量必须大于0,但真正可靠的是后端接口里的事务判断,前端校验只是提升体验。

入库单和出库单页面需要有点核心交互:点新增时弹出表单,表单里允许动态添加多行明细。用el-dialog加动态行的实现不难:维护一个tableData数组,每行有materialId、quantity、price。材料下拉选择后自动带出规格和单位。计算总金额放在前端展示,后端再算一遍并落库。这样页面看起来很有“业务系统”的感觉。动态行明细的核心结构大概是:

const detailRows = [ { materialId: null, materialName: '', spec: '', unit: '', quantity: null, price: null } ]; function addRow() { detailRows.push({ materialId: null, materialName: '', spec: '', unit: '', quantity: null, price: null }); } function removeRow(index) { detailRows.splice(index, 1); }

提交时把表头字段和detailRows一起post到后端。后端接收到的是一个包含主表信息和一个明细列表的JSON对象,正好对应前面设计的主从表结构。

4.4 前后端联调、跨域代理与打包

本地开发时前端和后端不在一个端口,会有跨域问题。最省事的方案是开启Vue开发服务器代理,以Vite为例,在vite.config.js中写:

server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, rewrite: path => path.replace(/^\/api/, '') } } }

后端接口如果统一是/api开头就按上面的写法,所有请求从浏览器发出时都指向前端端口,再由Node服务代理到后端。这样浏览器不会直接跨域,省去在后端配置CORS的麻烦。但要注意后端如果加了全局跨域处理,二者可以共存,只是不要两边配置冲突。打包时执行npm run build,生成的dist可以用Nginx托管。如果部署在服务器上,Nginx里要把前端路由回退到index.html:

location / { root /usr/share/nginx/html; try_files $uri $uri/ /index.html; }

否则刷新页面会出现404,这是毕设部署时最常见的坑之一。

5. 数据库脚本、接口文档和毕业论文:这些才是加分项

5.1 先定接口契约再写代码

前端和后端联调时最怕两个人各按各的想法来:后端把参数叫materialName,前端非要传name,接口反而调不通。所以开工前花半小时在ApiPost或Swagger里把接口清单整理出来,表格里写明路径、请求方式、入参出参。比如:

功能接口路径请求方式主要入参返回结果
分页查询材料/api/material/pagePOSTpage, size, name, categoryId表格数据+总数
新增入库单/api/stock/in/addPOST主表+明细列表成功或错误
库存预警列表/api/inventory/warningGET无低于安全库存数据

不要觉得花时间,这些表格稍作复制就能进论文的“接口设计”章节。更重要的是,接口统一之后前后端并行开发,谁也不等谁。

5.2 论文每一章该怎么组织

很多同学把论文放到最后一天写,这是最亏的一件事。写代码时的每一步决策都是论文素材,边做边记录会轻松很多。建议论文章节按:摘要、第一章绪论(背景、意义、国内外现状)、第二章技术介绍(SpringBoot、Vue、MySQL)、第三章需求分析(用例图、用例描述)、第四章系统设计(架构图、功能模块、数据库设计)、第五章系统实现(页面截图加核心代码讲解)、第六章系统测试(测试用例、功能测试、并发测试结论)、第七章总结与展望。

数据库设计那一章重点放E-R图和表结构说明,E-R图用ProcessOn画的截图就行,不用复杂工具。系统实现章节不要堆全部代码,挑两三个有代表性的写就行,比如登录验证、入库入库事务、动态添加入库单,其他地方用文字说明。测试章节可以用简单的功能测试表格,记录操作步骤、预期结果、实际结果。这些准备好之后,答辩时老师问什么都能有据可答。

5.3 演示数据和演示脚本

毕业答辩现场通常只有十来分钟,最怕的是准备的演示数据不够典型。不要在系统里放几十条毫无意义的数据,建议准备一组有故事的数据:三个供应商,六种材料,其中两种库存低于安全库存,一条入库单配三种材料。打开库存页面就能看到红色预警,打开库存预警页面有数据可数,打开入库单看得到明细和总金额。这样每个页面都有内容可以讲,也方便老师现场操作测试。

数据库脚本建议最后多跑几遍:删掉整个库,用source导入,再启动后端,看功能是否正常。这个流程如果能在答辩前跑通,就能排除“环境问题导致演示失败”的风险。

6. 常见问题与排查技巧实录

6.1 高频问题的按图索骥

开发过程中总有各种想不到的问题。我把自己和学生们在SpringBoot+Vue项目中常遇到的情况整理成了一张对照表,覆盖了从环境到部署的全流程:

现象常见原因解决办法
前端访问后端接口报跨域devServer代理没配置或请求路径不对检查proxy配置,确认后端接口是否统一前缀
MySQL连接失败:Public Key Retrieval is not allowedMySQL8连接配置缺allowPublicKeyRetrievalJDBC URL加上allowPublicKeyRetrieval=true&useSSL=false
页面刷新404前后端路由冲突,Nginx没有try_files配置location中try_files指向index.html
Java实体日期字段返回给前端格式不对没配Jackson日期格式在application.yml中配置日期格式或加@JsonFormat
后端返回null字段很多没打开驼峰映射或实体字段命名不一致配置map-underscore-to-camel-case=true
打包后前端接口地址还是localhost全局axios封装中baseURL写死改为环境变量VITE_API_BASE_URL,打包时替换
入库单插入成功但库存没变事务没有生效或库存更新逻辑在事务外Service方法加@Transactional,确认方法被代理调用
JWT过期后页面一直报错响应拦截器没处理401在响应拦截器里统一跳到登录页

这张表做出来之后,不仅是自己排查问题的索引,也是论文测试章节的素材。答辩时老师问“遇到过什么问题”,直接从这个表里挑两条讲,效果非常好。

6.2 最值得注意的三个设计教训

第一个是“别为了一点简单功能引入重型依赖”。有同学为了做图表统计,前端接了一大堆ECharts的懒加载和主题,后端接了一个Excel导出组件,结果版本互相冲突,最后全部删掉重来。毕业设计里能用原生表格、CSS和简单图表解决的就不要堆库,堆库反而容易变成事故现场。

第二个是“别把所有代码都放在一个Controller里”。刚写的时候图省事,用户管理、材料、供应商、出入库全部写在一个Controller里,后期改一个功能要翻几百行。后面重新拆分,花的时间比当时省下的一点要多得多。工程结构一开始就要分好,这是所有优秀项目的前提。

第三个是“写接口前一定要定好统一返回结构”。如果等联调时再改,前端要跟着改几十处,很容易心态崩。所以从第一个接口开始,就坚持使用Result包装,哪怕有的接口只返回一个提示文本,也要包一层。这个好习惯会让后期越写越顺手。

6.3 答辩前的最后一轮自测清单

建议答辩前按这个清单过一遍:删除数据库重建脚本能否一次性导入成功;用不同浏览器系统打开页面是否功能正常;正在使用的账号是否会在演示中途登录过期;库存预警数据是否存在;移动端缩放是否不影响核心操作;后端日志是否没有红色异常刷屏。每个功能都从上到下操作一遍,尤其是“新增入库单”这种带明细的功能,最好演示两次,确认数据正确更新到库存页面。

做完这些检查,剩下的重点就不是“能不能跑”,而是“能不能讲清楚”。建议把E-R图、功能结构图、核心接口列表打印出来放在手边。答辩的时候,先讲需求背景,再讲系统怎么设计,最后花两分钟演示核心流程,时间刚刚好。

这段项目做完之后,我最大的体会是:毕业设计更像是一次“把需求翻译成代码,再把代码翻译回文字”的训练。刚开始你可能只想着把所有页面做出来,但真正把它当成一个完整交付物对待后,你会发现,数据库脚本、接口文档、测试记录和工程规范这些“看不见”的工作,才是决定分数上限的东西。我也见过有人花一个星期把功能写完,却因为现场无法演示、论文全是流水账丢掉分数。其实只要在设计和联调阶段多留一份心,把每个关键决策都记录下来,答辩就会非常从容。希望这篇梳理能帮你把SpringBoot加Vue的第一套全栈系统做得更稳,也欢迎在真正动手时多想一步:库存账为什么这样存,接口为什么这样约定,事务为什么要放在这一层。想清楚这些问题,代码水平一定比只会照抄项目强得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询