ARTICLE DETAIL

资讯详情

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

SpringBoot+Vue仓库管理系统:表结构、权限与库存扣减实战解析

SpringBoot+Vue仓库管理系统:表结构、权限与库存扣减实战解析 简介基于SpringBootVue的粮食仓库管理系统资源包主要面向Java与Vue技术栈的开发者以及需要完成仓库管理类项目的学生。系统围绕粮食仓库的入库、出库、库存与用户管理场景后端采用SpringBoot前端采用Vue数据库使用MySQL包含仓库、入库单、出库单、用户、管理员、角色等核心数据表可支撑权限分配与日常出入库操作。压缩包内共2个文件包括SQL数据库脚本和TXT部署说明整体仅20KB轻量简洁便于快速导入数据库并对照说明配置运行环境。资源已有290人浏览学习适合初学者参考表结构设计与部署流程。通过该资源可以获得完整的数据库建表脚本节省手动设计表的时间配套部署说明梳理了从环境准备到启动的关键步骤帮助理解前后端分离项目与MySQL的整合方式从而提高项目复现效率是一份小而实用的仓库管理系统参考材料。1. 为什么这个 SpringBootVue 项目适合做粮食仓库管理系统的底子粮食仓库最容易出问题的不是粮食本身而是账面和实仓对不上。多个管理员同时填出入库单、Excel 被覆盖、临时工记错仓号这些现场问题比算法复杂多了。这个压缩包里的 SpringBootVue 系统把仓库、用户、角色、入库单、出库单拆成独立数据表用后端事务保证每次入库、出库都真实作用到库存余额上并且在源码里已经实现了登录、库存列表、单据增删改查这些主干功能。对正在做 SpringBoot 课程设计或者急着交付一套 Web 管理系统的开发者来说它的价值在于数据库脚本和部署说明是齐全的不需要自己从头搭权限模型拿到手先跑通再按自己的业务字段去改比照着一个空框架去发挥要快得多。后面几章我会把表结构、核心接口、部署配置和常见坑拆开讲。2. 数据模型与权限边界从 6 张表看粮食仓库的库存是怎么记账的2.1 SpringBootVue 前后端分离时的实体设计与表的对应关系这套系统采用前后端分离架构SpringBoot 只负责提供 REST 接口Vue 通过路由和组件渲染页面。在这种结构下数据库表的设计会直接决定接口是清爽还是混乱。经验做法是每个字典表对应一个管理页面每个单据表对应一个独立的增删改查流程。压缩包里的数据库脚本文件名是sprlsckglxthsg5642wncf.sql导入后默认建了 6 张业务表用户表、仓库表、入库单表、出库单表、管理员表和角色表。摘要里提到 5 张是笔误实际拆成 6 张原因在于管理员和用户虽然都存登录凭证但管理员需要维护的字段更多单拆出来让权限模型更清晰。表与 SpringBoot 实体类之间通常是一一映射t_warehouse对应Warehouse实体t_inbound对应InboundOrder实体。Vue 端的页面则不是严格按表划分而是按操作划分仓库列表页、新建入库单页、出库单审核页、用户管理页。这要求后端接口在返回数据时不能直接把实体类整个甩给前端而是用 DTO 把 sensitive 字段如密码去掉。我一般会在后端建VO对象只返回页面需要的仓库名称、库存余量、单位、更新时间等字段避免把数据库结构和前端展示强耦合。2.2 用户、管理员、角色三张表的权限边界权限设计上普通用户只允许查仓库、填写入库出库单管理员额外拥有修改仓库信息、管理用户和删除单据的权限。角色表里常见的字段是role_id、role_name、permissions其中permissions可以存逗号分隔的菜单编码也可以存一个 JSON 数组SpringBoot 后端在拦截器里解析这些编码判断当前请求是否匹配对应接口权限。这里有一个很容易被轻视的点前端按钮隐藏不等于后端安全。很多二次开发者在 Vue 里用v-ifrole admin隐藏删除按钮就以为完成了鉴权实际上攻击者直接通过接口工具调用删除接口照样能删。后端必须在 Controller 层或拦截器层对每个写操作做角色校验。建议权限逻辑按下列粒度设计表名关键字段权限粒度对应前端页面用户表user_id, username, password, contact登录后查看仓库和单据登录页、仓库列表管理员表admin_id, real_name, contact维护用户和仓库信息用户管理、仓库编辑角色表role_id, role_name, permissions控制菜单和按钮可见性路由菜单、按钮权限用户表和角色表用role_id关联管理员表同样持有role_id这样权限判断逻辑可以统一为从 token 中解析用户 ID 和角色 ID再到角色表查权限串最后比对请求路径前缀。比如仓库管理接口的路径都在/api/warehouse/**那么权限串里有warehouse:edit才允许执行修改操作。2.3 入库单、出库单与仓库表的关联方式入库单表的核心字段是inbound_no、warehouse_id、grain_type、quantity、inbound_time、operator出库单表在入库单基础上多一个receiver字段用来记录领粮单位或提货人。库存数量通常冗余在仓库表里比如warehouse表里有一个stock_quantity字段。这样查询库存时不需要 sum 所有流水性能更好但代价是必须保证流水写入和库存更新处于同一个数据库事务中。扣减库存时不能先查再更新因为并发下两个请求可能读到同一个旧库存然后各自做减法最终结果比实际余量多。更稳妥的做法是把判断条件写进 update 语句UPDATE warehouse SET stock_quantity stock_quantity - #{quantity} WHERE warehouse_id #{warehouseId} AND stock_quantity #{quantity}这条 SQL 的关键在AND stock_quantity #{quantity}MySQL 在执行 update 时会上锁只有满足库存充足条件才会真正更新。如果影响行数为 0说明库存不足或仓库不存在然后由 SpringBoot 抛出业务异常触发事务回滚这样就不会出现库存为负的情况。入库操作则是同一张表的stock_quantity quantity同样要在事务内执行。这套系统的建表脚本里已经包含了这些表的字段定义如果你打算扩展批次号或者质检报告字段直接在原表上增加列即可不需要改动流水逻辑。3. 登录鉴权、库存分页和出入库扣减的实现细节与参数约定3.1 登录鉴权SpringBoot 的 Token 校验与 Vue 路由守卫登录模块在 Web 管理系统里属于看起来简单、细节点最多的部分。这套粮食仓库系统的登录逻辑是前端提交用户名和密码后端校验通过后生成 token 返回前端把 token 存在 localStorage 或 pinia 中随后所有请求自动携带。后端登录接口的典型写法如下PostMapping(/login) public Result login(RequestBody LoginDTO dto) { User user userMapper.findByUsername(dto.getUsername()); if (user null || !BCrypt.checkpw(dto.getPassword(), user.getPassword())) { return Result.error(用户名或密码错误); } String token jwtUtil.createToken(user.getUserId(), user.getRoleId()); return Result.success(token); }上述代码中有两个关键点。第一密码存储要使用 BCrypt 哈希数据库中不存明文这样即使数据库脚本泄露拿到的也只是不可逆的哈希值。第二token 里只放用户 ID 和角色 ID不要把密码、联系方式这类敏感信息放进 JWT 载荷因为 JWT 默认是 Base64 编码任何人都能解码看到内容。前端收到 token 后在 axios 请求拦截器里设置请求头axios.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] Bearer token } return config })同时 Vue 路由守卫负责未登录跳转router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.path ! /login !token) { next(/login) } else { next() } })这里的逻辑是只做前端拦截真正安全性还是靠 SpringBoot 拦截器校验 token否则用户强制修改 localStorage 状态也不可能进入管理系统。3.2 库存分页查询SpringBoot 分页插件与 Vue 参数约定仓库列表页通常会展示名称、地点、粮食品种、库存数量、更新时间等信息数据多了以后不能全量加载必须分页。后端采用 MyBatis-Plus 分页插件时接口接收pageNum、pageSize、keyword三个核心参数。常见写法如下GetMapping(/warehouse/page) public Result page(RequestParam(defaultValue 1) long pageNum, RequestParam(defaultValue 10) long pageSize, RequestParam(required false) String keyword) { PageWarehouse page new Page(pageNum, pageSize); LambdaQueryWrapperWarehouse wrapper new LambdaQueryWrapper(); wrapper.like(StringUtils.hasText(keyword), Warehouse::getName, keyword); wrapper.orderByDesc(Warehouse::getUpdateTime); PageWarehouse result warehouseMapper.selectPage(page, wrapper); return Result.success(result); }前端调用时需按后端约定传参否则会出现第一页数据永远正确、第二页却返回空的情况。参数约定必须一致参数名类型是否必填说明pageNuminteger否默认 1当前页码pageSizeinteger否默认 10每页条数建议后端限制最大 100keywordstring否仓库名称模糊搜索如果前端把pageNum写成page后端又是按照pageNum接收MyBatis-Plus 不会报错而是把page当成默认值 1 处理所以调试时先看请求 URL 的 query 参数是不是和后端方法参数名一致。Vue 端一般从表格组件里的current-change事件读取新页码再重新请求接口。3.3 出入库防重复提交与库存扣减的并发控制入库和出库不只是插入一条记录还需要同步修改仓库表的库存字段这两个操作必须放在同一事务里避免出现单据成功但库存不更新、或者库存更新了却没有对应单据的情况。SpringBoot 中可以用Transactional实现Transactional(rollbackFor Exception.class) public void inbound(InboundDTO dto) { InboundOrder order new InboundOrder(); order.setInboundNo(IN System.currentTimeMillis()); order.setWarehouseId(dto.getWarehouseId()); order.setQuantity(dto.getQuantity()); inboundMapper.insert(order); warehouseMapper.increaseStock(dto.getWarehouseId(), dto.getQuantity()); }出库逻辑要多一个库存判断Transactional(rollbackFor Exception.class) public void outbound(OutboundDTO dto) { OutboundOrder order new OutboundOrder(); order.setOutboundNo(OUT System.currentTimeMillis()); order.setWarehouseId(dto.getWarehouseId()); order.setQuantity(dto.getQuantity()); int rows warehouseMapper.decreaseStock(dto.getWarehouseId(), dto.getQuantity()); if (rows 0) { throw new RuntimeException(库存不足或仓库不存在); } outboundMapper.insert(order); }这里的decreaseStock就是前文提到的带stock_quantity #{quantity}条件的 update 语句。判断rows 0的那个地方很关键如果放在 insert 之后事务回滚时还要多插入一条无效单据虽然最终会回滚但语义上不清晰。此外前端按钮防重复点击不能完全依赖loading比如用户双击提交时两次请求都可能进入后端所以在出入库单号字段上最好加唯一索引索引冲突时数据库会直接拒绝第二次插入这是最后一道防线。4. 把源码包部署到本机的完整配置数据库脚本、application.yml 与 Vue 前端4.1 导入 sprlsckglxthsg5642wncf.sql 数据库脚本拿到压缩包后先把sprlsckglxthsg5642wncf.sql导入到本地 MySQL。使用命令行导入时先建库再指定字符集mysql -u root -p CREATE DATABASE grain_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; exit mysql -u root -p grain_db sprlsckglxthsg5642wncf.sql如果使用 Navicat直接新建数据库连接选择grain_db然后右键运行 SQL 文件。导入完成后检查一下表数量确认 6 张表全都存在。如果表数量不对大概率是脚本中出现了重复建表语句或者断点需要在运行前用文本编辑器打开 SQL 文件搜索CREATE TABLE逐条核对表名。这里的utf8mb4字符集很关键如果使用默认的utf8存入生僻字或特殊符号时会报Incorrect string value错误。导入成功之后再检查管理员表的初始记录。很多源码包会预置一个admin/admin123之类的账号如果你用系统里已经记录的 BCrypt 哈希注意密码可能是加密后的字符串不要手动改成明文。手工把密码字段改成明文后后端用 BCrypt 校验时永远无法匹配。4.2 SpringBoot 的 application.yml 核心配置项后端项目启动前必须修改application.yml主要是数据源配置。默认配置里写的可能不是本机信息直接启动会报数据库连接失败。对应配置如下server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/grain_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: your_password jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: id-type: auto参数说明如下serverTimezone必须设置否则新版 MySQL 驱动会报The server time zone value Öйú±ê׼ʱ¼ä is unrecognizeduseUnicode和characterEncoding控制中文编码不设置会出现乱码jackson的时间格式决定接口返回的时间字段是2025-01-15 10:00:00还是时间戳前端如果显示 NAN多半是这里没配置。log-impl配置成StdOutImpl后每执行一条 SQL 都会打印到控制台排查问题时很方便但生产环境建议去掉它避免日志刷屏。注意如果你的 SpringBoot 项目里用了 yml 加密插件比如 jasypt那么配置项可能是ENC(xxxx)的形式这时还需要配置解密密钥否则启动时会直接报错。这个系统默认不强制加密但二次开发时如果看到密文不要随便替换成明文。4.3 Vue 前端安装依赖与反向代理配置前端目录单独放在解压后的web或者frontend文件夹中。首次运行需要安装依赖npm install npm run serve如果npm install速度慢或者卡住可以临时切换国内 npm 镜像npm config set registry https://registry.npmmirror.com npm installVue 项目默认端口通常是 8080而 SpringBoot 后端也占用 8080这时需要修改 Vue 的端口或者在vue.config.js中配置 devServer。比较推荐的做法是用反向代理把/api开头的请求转发到 SpringBoot 的 8080 端口// vue.config.js module.exports { devServer: { port: 3000, proxy: { /api: { target: http://localhost:8080, changeOrigin: true, pathRewrite: { ^/api: } } } } }配置完成后前端页面访问http://localhost:3000请求/api/warehouse/page会被代理到http://localhost:8080/warehouse/page。这里必须注意pathRewrite如果后端 Controller 的路径本身是/warehouse/page而前端统一加上了/api前缀不重写的话会变成 404。如果去掉changeOrigin或设为false跨域请求可能因为 Host 头不一致被后端拒绝。本地部署时的常用配置项汇总如下配置位置配置项推荐值作用application.ymlserver.port8080后端端口application.ymlspring.datasource.urljdbc:mysql://localhost:3306/grain_db数据库连接vue.config.jsdevServer.port3000前端端口vue.config.jsproxy.targethttp://localhost:8080接口转发地址后端采用 Maven 管理时启动前先执行mvn clean package -DskipTests java -jar target/warehouse-system.jar这里跳过测试是为了防止测试环境连接不上数据库导致打包失败真正部署时再根据实际情况保留测试。5. 跑起来之后建议先做这三个验证再谈二次开发系统能登录、能查询列表并不代表它已经可以上线。我拿到这类源码包习惯先验证三个最容易被表面现象掩盖的功能点。第一个是刷新页面后登录状态是否还在。正常情况下 token 存在 localStorage 里刷新后路由守卫能读取到用户不需要重新登录。如果刷新就跳回登录页检查 Vue 路由守卫的 token 读取位置是否在 pinia 初始化之前或者是否误用了 sessionStorage。第二个是库存扣减的并发正确性。打开两个浏览器窗口用同一账号对同一个仓库同时提交出库 50 吨而仓库库存只剩 80 吨最终结果应该是只有一单成功另一单提示库存不足。如果你的系统两次都成功说明decreaseStock里没有加stock_quantity #{quantity}条件需要补上。第三个是删除仓库前是否校验关联单据。如果直接删除一个仍有入库记录的仓库外键约束会报错或者产生孤儿数据业务上应该提示管理员先处理库存。二次开发时有一个经常被忽略的小地方出入库单号不要只用System.currentTimeMillis()同一毫秒内两次请求可能生成相同单号。建议改成日期 随机数或者在后端引入 Redis 自增序号。如果你不想引入 Redis最简单的方式是在 Java 里用AtomicLong配合日期前缀生成单号。如果源码包里还保留了支付模块相关代码而你的场景用不上删除时需要注意把数据库里的支付关联字段一并去掉否则后台管理页会因为缺字段而报错。除此之外把时间字段都改成LocalDateTime而不是Date这样在 SpringBoot 的application.yml里配置的日期格式才能生效接口返回给 Vue 的时间也不会出现时区偏移。这套系统的边界在于它适合中小型仓库管理、课程设计和快速毕设改造字段数量和业务深度都足够支撑二次扩展真正要应对多粮库、多业务线的复杂场景时还需要引入更细粒度的批次管理、质检记录和审批流。本文还有配套的精品资源点击获取
返回列表