资讯动态

SpringBoot+Vue企业级物资管理系统全栈实战

发布时间:2026/9/9 7:38:38 来源:尧图企业网站定制
今年整理项目源码时翻出这套早期做的企业级物资管理系统基于 SpringBoot Vue MyBatis MySQL 的经典前后端分离架构当时用于企业防疫物资的入库、领用、库存预警和台账统计。虽然现在这套系统被我改成了通用版物资管理但核心代码结构没变拿来当 SpringBoot 全栈练手项目或者直接改造复用都合适。这篇文章不打算只贴代码结构和功能列表我把自己当时从设计表结构、开发后端接口、写前端页面到部署上线的整个过程包括踩过的坑、优化过的细节全都梳理一遍。如果你是刚学完 SpringBoot 和 Vue 想找个完整项目练手或者公司需要一套物资管理系统但预算有限想自己折腾这篇值得认真看完。1. 这套源码解决的真实痛点与技术选型逻辑1.1 为什么市面上模板代码很多真正能落地的不多很多人拿到一个所谓“完整管理系统源码”打开一看是 MyEclipse 时代的 SSH 项目或者前端还是 JSP想改点东西都费劲。还有一种是代码结构完全混乱Controller 里写 SQL业务逻辑全部堆在页面里根本没法维护。这套系统的原始需求很明确企业要管理口罩、消毒液、防护服这类物资的库存需要记录每一次入库和领用记录实时掌握当前库存数量并能在库存低于阈值时自动预警。看起来简单但真做起来有不少细节——物资种类多、批次不一、领用频繁还要按部门统计数据要有追溯性。我当时的判断是这类企业内部管理系统业务逻辑复杂但技术难度不算高关键是要结构清晰、易于二次开发。用 SpringBoot 负责后端接口Vue 负责前端页面MyBatis 负责数据持久层MySQL 存储数据这是最稳妥也最容易被团队接手的搭配。1.2 SpringBootVueMyBatisMySQL 组合的底层合理性先聊后端。SpringBoot 简化了 Spring 的配置流程内嵌 Tomcat打包成 jar 就能直接跑这对中小企业的部署环境特别友好——不需要单独配置外部容器服务器上装个 JDK 就完事。MyBatis 相比 JPA/Hibernate 在这类场景的优势是 SQL 完全可控物资管理里经常涉及多表关联查询、按时间范围聚合统计、动态条件筛选这些用 MyBatis 的 XML 映射文件写起来非常顺手排查问题时也能直接拿出一条 SQL 来分析。比如统计某个月各部门领用数量大概是这样的查询逻辑SELECT department_name, material_name, SUM(quantity) AS total_quantity FROM stock_record WHERE record_type OUT AND create_time BETWEEN #{startTime} AND #{endTime} GROUP BY department_name, material_name ORDER BY total_quantity DESC这种带条件的动态统计在 MyBatis 里用where和if标签可以灵活拼装还能用 include 抽取重复的 SQL 片段。前端选 Vue 是因为它组件化开发非常适合管理系统这类页面。像物资台账、出入库表单、库存看板每个功能模块封装成独立组件互不干扰。配合 Element UI 组件库表格、弹窗、表单校验这些后台管理的常见交互都能快速搞定不用从零写 CSS 和交互逻辑。如果你用的 Vue 3新版本还支持 Composition API代码复用性和 TypeScript 支持都有明显提升但这套老系统用的是 Vue 2 Element UI主要考虑是当时生态最成熟。这套技术栈的另一个优势是招聘市场上会的人多接手维护成本低对企业来说这意味着后续修改功能不需要依赖源码作者。2. 从业务梳理到数据库设计物资台账与出入库流转2.1 核心业务域拆解开发前第一件事不是建工程写代码而是把业务捋清楚。我把物资管理系统拆成了四个核心域库存域物资信息、当前库存、库存预警阈值。流转域入库单、领用单、调拨单每次流转都会留下一条台账记录。组织域部门、用户、角色不同角色权限不同。统计域按时间、部门、物资种类多维度的数据汇总。这四个域对应到数据库里就是一组相互关联的表。其中最关键的设计决策是库存表只存当前数量所有历史变动全部落在流转记录表里。这样做的好处是任何一个数量变化都能追溯到具体的操作记录对后期审计非常有利。2.2 表结构设计要点与索引策略核心表我设计了下面六张这里直接列出关键字段material物资表。id、name、specification、unit、stock_quantity、lower_limit、status。stock_record出入库记录表。id、material_id、record_typeIN 或 OUT、quantity、department_id、operator_id、remark、create_time。department部门表。id、name、parent_id。sys_user用户表。id、username、password、real_name、department_id、role_id。sys_role角色表。id、role_code、role_name。另外还有一张字典表用来维护物资分类、计量单位等通用数据。表结构设计中有几个容易被忽略的点我单独说一下。第一物资表里冗余了 stock_quantity 字段。正常情况下库存量可以通过出入库记录实时计算出来但每次都做全表聚合查询性能开销很大。保留冗余字段每次出入库操作后在事务里同步更新这个值查询时就能直接取单条 SQL 搞定。第二stock_record 表必须建联合索引。我建的是idx_material_time(material_id, create_time)这样按物资查询时间范围内的出入库记录时能走索引避免全表扫描。还要单独建一个idx_record_type(record_type)索引因为按出库类型统计是最常见的查询条件。第三所有金额、数量字段都用 DECIMAL 而不是 FLOAT/DOUBLE。虽然物资管理的数量一般不会出现二进制浮点误差问题但规范一点没坏处而且 DECIMAL 在报表统计时不会出现小数点后面一堆 9 的情况。CREATE TABLE stock_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, material_id BIGINT NOT NULL COMMENT 物资ID, record_type VARCHAR(10) NOT NULL COMMENT in-入库out-出库, quantity INT NOT NULL COMMENT 数量, department_id BIGINT COMMENT 领用部门ID, operator_id BIGINT NOT NULL COMMENT 操作人ID, remark VARCHAR(255) COMMENT 备注, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, KEY idx_material_time (material_id, create_time), KEY idx_record_type (record_type) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT出入库记录表;这里要注意MySQL 如果版本比较老utf8mb4 的索引长度限制会导致建索引失败建议建表前先把字符集和排序规则统一设置好。2.3 几个必须处理的边界数据场景表结构设计完有几个边界场景必须在编码前想清楚否则后面写业务逻辑时很容易出漏洞。场景一是删除约束。部门可能被删除但历史出入库记录还引用着这个部门的 ID如果直接物理删除历史数据就断了。我的做法是给部门表加一个deleted逻辑删除标记物理数据永远保留查询时只过滤有效数据。场景二是物资下架。某类物资不再使用不能直接从物资表删除因为历史记录还关联着它。做法和部门一样用状态字段标记为“停用”前端展示时过滤掉但历史数据仍然完整可查。场景三是负数库存。库存扣减时如果不对数量做校验很容易出现并发请求导致库存变为负数。这个问题我在后面专门讲解表结构层面能做的限制是给stock_quantity字段加UNSIGNED约束但更关键的还是在业务代码层做控制。3. 后端实现细节事务、并发扣减与权限控制3.1 标准分层结构与关键配置后端代码我严格按照 Controller → Service → Mapper 三层结构组织严禁在 Controller 里写业务逻辑。Controller 只做参数接收和结果封装Service 处理业务规则和数据事务Mapper 负责数据库操作。这样做的直接好处是假如要把 Service 层拆成微服务只需要把接口暴露出去就行改动量很小。application.yml里有几个配置值得注意spring: datasource: url: jdbc:mysql://localhost:3306/material_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: root123 driver-class-name: com.mysql.cj.jdbc.Driver jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.material.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl这个配置里有几个关键项第一个是时区之前不设置serverTimezone经常出现时间差 8 小时的问题第二个是map-underscore-to-camel-case数据库字段下划线命名可以自动映射成 Java 的驼峰属性免去大量 resultMap 配置第三个是 SQL 日志打印开发阶段打开方便调试生产环境建议关闭。3.2 库存扣减的并发安全实现这是整个系统技术含量最高的地方值得单独拉出来讲。想一想这个场景库存剩余 10 件两个员工同时提交领用申请各领 8 件。如果没有并发控制两个请求都可能先查到库存是 10然后各自扣减 8最后库存变成 2 而不是负数但实际库存已经被超卖了。我采用了乐观锁方案在物资表增加一个version字段每次更新库存时附带版本号条件UPDATE material SET stock_quantity stock_quantity - #{quantity}, version version 1 WHERE id #{materialId} AND version #{version} AND stock_quantity #{quantity}这段 SQL 的含义是只有当库存数量足够且版本号没变时才执行扣减。MyBatis 中受影响行数为 0 时抛出异常或重试。我把核心操作放在 Service 层的Transactional事务里同时完成扣减库存和插入出入库记录保证两个操作要么同时成功要么同时失败。还有一个容易被忽略的细节stock_quantity字段的精度。如果物资按重量管理比如消毒液按升浮点数参与加法减法误差会累积所以我把数量字段统一设计为整数最小计量单位例如 1.5 升按 1500 毫升管理。这个设计初期可能不觉得重要但用到复杂报表统计时就体现价值了。3.3 基于 RBAC 的权限拦截与数据隔离企业管理系统一般都要求不同角色看到不同内容。我实现的是最经典的 RBAC基于角色的访问控制模型用户 → 角色 → 权限。权限拦截是通过 SpringBoot 拦截器做的核心代码如下public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String token request.getHeader(Authorization); if (token null || !TokenUtil.verify(token)) { response.setStatus(401); return false; } // 解析 token 获取用户信息放入 ThreadLocal 供业务层使用 UserContext.set(TokenUtil.parse(token)); return true; } }登录成功时签发一个 Token 返回前端前端把 Token 存在本地每次请求放在请求头里。后端拦截器统一校验没有 Token 直接返回 401 状态码让前端跳转登录页。这里我踩过一个坑接口权限只做了登录拦截没有细粒度到菜单和按钮级权限。后来业务提需求说“普通员工不能看到库存预警设置”我不得不在前端再做一层路由守卫和菜单过滤同时在每个接口上加RequiresPermission注解总共改了两三天。如果你打算直接拿这套源码改造成公司内部系统强烈建议先把“接口权限”和“菜单权限”的数据模型设计好Role 表关联权限表再关联接口和按钮标识。用户数据隔离方面普通用户只能看到自己部门相关操作记录管理员可以查看全部我在stock_record查询接口中加入当前用户的部门 ID 过滤条件。这个设计不是最优的复杂报表查询时需要跨部门汇总但优点是实现简单、不容易越权。4. 前端实现细节Vue 工程化与核心页面交互4.1 工程目录与鉴权链路前端用 Vue 脚手架创建工程目录按功能模块划分主干如下src/api封装所有后端接口请求。src/router路由配置包含动态路由和路由守卫。src/storeVuex 状态管理存储用户信息、Token、菜单权限。src/views页面组件按模块分目录。src/utils封装的 axios 实例、工具函数等。关键的鉴权链路在router/index.js里用全局前置守卫拦截未登录的访问router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.path ! /login !token) { next(/login) } else { next() } })axios 封装时加请求拦截器统一在请求头携带 Token响应拦截器处理后端返回的统一结构拿到 HTTP 401 时清理本地 Token 并跳转登录页。这样每写一个接口不需要重复判断登录状态代码干净不少。4.2 出入库操作、库存看板等核心页面拆解系统页面按业务场景拆分为三个核心物资台账、出入库登记、统计看板。物资台账页面用 Element UI 的el-table分页展示表格上方提供搜索条件——物资名称、分类、状态。这里有一个被我反复调优的交互细节搜索按钮触发后必须重置分页页码为 1否则在搜索条件下用户在第 5 页搜索拿到的是第 5 页数据看起来就像搜不到结果这个问题很隐蔽但极易出现。出入库登记页是操作最频繁的页面。表单里有物资选择、数量、部门、备注等字段。注意两个细节一是物资选择下拉框如果物资种类多几百个一次性拉全部选项会卡死前端应该使用远程搜索组件输入关键字再去后端查二是数量输入框必须校验必须为正整数我在前端用rules校验后端再校验一遍防止绕过前端直接调接口导致脏数据。库存看板部分我用了 ECharts 来做柱状图展示库存数量和预警情况。这个页面还有一个功能库存低于预警阈值的物资在台账页列表中用红色高亮显示。当时看到这个高亮效果时我意识到“数据可视化对于物资管理类系统真的很关键管理者需要一眼看出问题而不是从表格里一个个找”。Vue 前端在首次加载时还会根据当前用户的角色动态渲染菜单这部分是通过前端代码模拟的menuList过滤实现的没有做真正的动态路由。5. 完整源码跑通指南从本地环境到实际部署5.1 环境准备与初始化脚本拿到源码之后第一件事不是运行而是把环境准备好。需要安装 JDK 1.8 以上、Maven 3.6 以上、MySQL 5.7 以上、Node.js 14 以上。MySQL 安装完有两个地方新手容易踩坑。一个是 root 密码认证插件问题MySQL 8.x 默认使用caching_sha2_password老版本 JDBC 驱动不兼容运行报错找不到驱动或连接被拒绝。解决办法是下载最新版 MySQL Connector/J或者用下面命令把认证方式改回mysql_native_passwordALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码; FLUSH PRIVILEGES;另一个是数据库字符集建库时建议显式指定CREATE DATABASE material_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;然后把项目里的material_db.sql导入。这个脚本里包含建表语句和部分测试数据测试数据包含用户、部门、物资、出入库记录等能保证运行起来就能看到效果。后端配置里数据库连接信息需要改成你自己的用户名密码改完启动 SpringBoot 应用项目默认端口 8080。前端在项目根目录执行npm install这一步如果网络环境不稳建议配置 npm 镜像源然后执行npm run dev启动开发服务器。默认端口是 3000。5.2 SpringBoot 与 Vue 的联调与部署开发模式下前端和后端端口不同需要配置跨域否则浏览器会拦截接口请求。我后端加了一个全局 CORS 配置类Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) .allowedOrigins(http://localhost:3000) .allowedMethods(GET, POST, PUT, DELETE); } }如果后端日志或浏览器控制台显示“No Access-Control-Allow-Origin header is present”基本都是这个配置没生效。生产环境部署时前端执行npm run build打包生成dist目录下的静态文件。把静态文件放在 Nginx 的 html 目录下同时配置反向代理把/api路径转发到 SpringBoot 服务server { listen 80; server_name your-domain.com; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; # 解决刷新404问题 } location /api/ { proxy_pass http://localhost:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }后端打成 jar 包用 nohup 启动nohup java -jar material-system.jar --spring.profiles.activeprod app.log 21 5.3 首次启动最常见的四个报错这部分是我根据自己带新同事、朋友拿源码跑时遇到的真实问题整理的新手经常在这几个位置卡住。报错一数据库密码包含特殊字符。比如密码是abc123在 yml 里直接写的话 符号会被当成参数占位符解析导致连接失败。解决方法是把密码用单引号包起来或者改用环境变量注入避免明文写在配置里。报错二MyBatis 绑定异常提示Invalid bound statement (not found)。一般有三种情况mapper-locations配置的路径和 XML 实际位置不一致XML 文件没在 Maven 打包时被拷贝到 classpath接口和 XML 命名空间没对应上。检查第一个的时候注意路径写成classpath*:mapper/*.xml更安全能兼容多模块场景。报错三启动端口被占用。SpringBoot 默认 8080本地已经跑着别的应用时启动失败。临时解决可以加启动参数--server.port8081也可以把端口配置写到 yml 里。报错四前端依赖装不上。npm install 报各种奇怪错误大概率是 node-sass 这类依赖需要编译和本地 Node 版本不兼容。建议直接用镜像源然后检查 Node 版本是否在项目要求的范围内。如果项目里是 node-sass我后面已经改成 sassdart-sass了问题彻底解决。6. 企业级项目的进阶优化方向与源码阅读建议6.1 性能与扩展性的演进路线这套系统用在小规模场景非常流畅但你要是想把它改造升级成支撑更大规模业务的系统有几个方向值得考虑。第一个是缓存层。目前所有查询都直连 MySQL在高并发场景下数据库压力很大。比如库存看板这种所有人都要看的页面可以引入 Redis 缓存把聚合查询结果缓存 30 秒大大减少数据库压力。具体实现可以基于注解做框架封装但这套系统里没有引入 Redis需要你自己扩展。第二个是异步化。出入库操作本身要求事务一致性但操作成功后的短信通知、消息推送、日志审计不需要同步完成。把这些动作改成发消息到 MQ 或者 Spring 自带的异步事件处理可以显著缩短接口响应时间。第三个是数据库层面的考虑。当数据量达到百万级单表查询会明显变慢可以考虑按时间分表比如 stock_record 按月分表或者引入读写分离。不过对绝大多数使用场景来说加索引和 SQL 优化足够解决 99% 的性能问题不用一上来就整这些复杂的架构。6.2 安全加固方向企业级系统上线前必须做安全检查至少以下三个位置不能有漏洞。第一是 SQL 注入。MyBatis 框架用#{}传参时是预编译天然防注入但有人写成${}直接拼字符串那就是个大漏洞。搜索项目里所有${}的用法除了 ORDER BY 字段名和列名这种无法用预编译的场景其他全部改掉。ORDER BY 字段名也不能乱拼最好是前端传字段白名单后端做映射不合法就拒绝请求。第二是越权访问。现在接口只校验“是否登录”没区分“这个用户有没有权限操作这个数据”。例如一个普通用户拿到了领用记录的接口 URL直接绕过前端就能调接口无限领用。建议做数据权限用户只能查询自己部门的数据管理员全部数据这个我在前面 RBAC 部分讲过了改造的时候优先做这个。第三是密码安全。现在用户表存的是密码明文或简单哈希这是非常危险的。改成 BCrypt 加密存储增加随机盐。注册或修改密码时前端拿到明文后端用 BCrypt 加密后存库。登录校验时用matches方法比对不要自己发明加密算法。6.3 拿到源码后怎么读代码最快如果你刚拿到这套源码我建议不要从启动开始读也不要按 package 顺序从上往下读效率太低。我的建议是先跑起来然后用一个完整业务串联代码。比如“入库一批口罩”这个操作从前端点击“入库”按钮开始跟踪到接口请求怎么发出、后端 Controller 怎么接收、Service 怎么实现事务逻辑、Mapper 怎么更新数据一条线读完就掌握了整个系统的核心链路。第二遍读“登录 权限”这条线弄明白 Token 怎么签发、拦截器怎么拦截、权限怎么控制。这两条链弄明白这套系统的骨架就拿下了。剩下其他模块都是围绕相似逻辑在重复阅读速度会快很多。读代码的时候重点关注几个关键类TokenUtil、StockService、AuthInterceptor、MaterialController这几个类是系统中复用率最高的核心模块。最后再说一个心态问题。很多人拿到完整项目源码第一反应是“我把它运行起来就好了”然后跑起来就不知道怎么继续学了。真正让你技术提升的不是跑起来那个瞬间而是把代码改坏再修好的过程。你可以试试把库存扣减里的乐观锁去掉然后并发请求测试感受一下数据错乱是什么样子再改回来这个过程比看十遍代码都有用——一定不要只停留在“能运行”的层面要主动去改、去拆、去踩坑技术才能变成自己的。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价