拓冰建站拓冰建站
首页 / 资讯中心 / 正文

SpringBoot+Vue办公用品管理系统:并发库存与审批流程实践

简介一份面向Java毕业设计场景的办公用品管理系统参考论文适合计算机相关专业学生及初级开发者借鉴。论文基于SpringBootVue框架与MySQL数据库设计系统涵盖采购、库存、分发、统计等核心功能并从前端页面到后台接口、数据表设计均有说明。正文从绪论、相关技术及环境说明、需求分析等方面展开包含研究背景、国内外现状、可行性分析、总体设计、编码实现与软件测试等内容结构规范、层次清楚可作为撰写毕业设计论文或课程报告的模板参考。压缩包内为1个docx文件大小1.96MB便于阅读和编辑。该资料已有91人学习适合用于补充系统设计思路与文档撰写细节帮助提升毕业设计效率。1. 为什么办公用品管理系统会同时选中 SpringBoot 与 Vue办公用品管理系统是 Java 毕设里最常见的题目但很多初学项目做到一半就卡在“库存对不上”和“审批状态乱”这两个地方。原因在于这个系统本质上是一个带并发写入的流程系统不是简单的增删改查。Spring Boot 负责把库存扣减、申请审批、事务回滚这类后端逻辑收拢成独立服务Vue 则专门处理申请页面的多步交互、审批按钮和统计图表展示。前后端分离后业务边界清楚也可以独立部署。接下来的内容会沿着数据模型、Spring Boot 接口、Vue 页面、测试部署这条主线把新手容易翻车的地方逐一拆开。2. 办公用品管理系统的数据模型设计先定领用流程再拆表结构2.1 从办公用品领用流程反推业务对象办公用品管理系统的最小闭环是员工登录→查看办公用品库存→发起领用申请→管理员审批→出库或驳回。再往下扩展还会有采购入库、供应商管理、部门领用统计、低库存预警。做第一版时我一般不建议一上来就把权限系统做得特别重而是先把四个核心对象定清楚用户User、办公用品Item、领用申请Apply、审批记录Approval。普通员工和管理员用role字段区分即可等系统跑通后再引入 Spring Security 的完整 RBAC。这样定义后状态流转就会非常清晰。领用申请的状态设计为PENDING待审批、APPROVED已通过、REJECTED已驳回、OUTBOUND已出库。这里特别要区分 APPROVED 和 OUTBOUND审批通过只代表管理员同意实物还没被领走所以不能扣库存只有出库操作才真正减库存。很多毕设库存对不上正是因为把“审批通过”和“库存扣减”绑在了同一个动作里用户申请通过后一直不来领库存就提前少了。2.2 核心表结构与字段设计下面是一个最小可用的建表模型MySQL 8.0 直接执行即可。我把办公用品表t_item和库存表合并为一对一关系这样查询用品列表时不需要联表就能拿到库存数量代价是如果需要记录每一次入库流水需要再建一张流水表。对于第一版论文系统这种设计足够清晰。CREATE TABLE t_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(32) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, role TINYINT NOT NULL DEFAULT 1 COMMENT 0-管理员 1-普通员工, department VARCHAR(50) DEFAULT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE t_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(64) NOT NULL, spec VARCHAR(128) DEFAULT NULL COMMENT 规格型号, unit VARCHAR(16) NOT NULL COMMENT 单位盒/支/个, stock INT NOT NULL DEFAULT 0, low_stock INT NOT NULL DEFAULT 5 COMMENT 低库存预警阈值, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); CREATE TABLE t_apply ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, item_id BIGINT NOT NULL, apply_count INT NOT NULL COMMENT 申请数量, status VARCHAR(20) NOT NULL DEFAULT PENDING, reason VARCHAR(255) DEFAULT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_apply_user (user_id), KEY idx_apply_status (status) ); CREATE TABLE t_approval ( id BIGINT PRIMARY KEY AUTO_INCREMENT, apply_id BIGINT NOT NULL UNIQUE, approver_id BIGINT NOT NULL, action VARCHAR(20) NOT NULL COMMENT APPROVE/REJECT, comment VARCHAR(255) DEFAULT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP );表结构里最需要关注的是t_apply.status和t_item.stock这两个字段它们的组合关系决定了库存是否正确。状态/字段示例值说明t_apply.statusPENDING初始状态前端显示“待审批”t_apply.statusAPPROVED管理员同意未出库不扣库存t_apply.statusOUTBOUND已出库此时库存才扣减t_apply.statusREJECTED已驳回不产生库存变化t_item.stock12当前剩余数量t_item.low_stock5小于等于该值触发预警状态流转可以这样约束PENDING 只能转为 APPROVED 或 REJECTEDAPPROVED 只能转为 OUTBOUNDREJECTED 和 OUTBOUND 是终态。这个约束不需要写在数据库里而是在 Service 层用if判断避免用数据库触发器增加维护成本。另外还有一个容易忽略的校验申请数量不能超过库存。如果这个校验只做在前端绕过页面直接调接口就失效了所以后端 Service 必须重复校验。但只有这一层校验还不够两个并发请求同时读取到库存为 5同时申请 5 个都会通过校验最后库存变成负数。并发问题放到第 3 章详细讲。2.3 用 MyBatis-Plus 还是 Spring Data JPA这个选型直接影响后面代码量。Spring Data JPA 上手快实体类加注解后自动建表适合快速原型但复杂统计要写 JPQL比如“每个部门领用排行”这种需求就不好写。MyBatis-Plus 更贴近 SQL 习惯LambdaQueryWrapper直接拼条件代码生成器也能生成单表 CRUD目前是大多数 Java 毕设和中小型管理系统的默认选型。如果这个系统后面要加多表关联的领用统计我会选择 MyBatis-Plus。一个反直觉的结论无论选哪个都不建议在 Controller 里直接操作多个 Mapper 或 Repository 做跨表事务。正确做法是在 Service 层加Transactional把“更新库存”和“生成出库记录”放在同一个事务方法内。这样可以保证库存扣减成功的同时记录一定存在不会出现扣了库存但记录丢了的情况。3. 用 Spring Boot 实现库存扣减与领用审批的后端服务3.1 项目初始化与依赖选型避开 SpringBoot 版本太高的问题初始化 Spring Boot 项目时直接用 IDEA 的 Spring Initializr 生成工程依赖选择spring-boot-starter-web、MySQL、MyBatis-Plus 等。很多新手喜欢选最新版本结果遇到 MyBatis-Plus 或某些第三方 starter 尚未适配的情况报错找不到数据源配置。实际项目里我会选择最新稳定版的“上一个次版本”比如当前稳定版是 3.3.x我可能选 3.2.x 来保证兼容。这个问题在springboot 面试题里也常被问到版本太高时第一件事不是改代码而是检查 starter 的兼容矩阵。pom.xml里关键的依赖如下parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.5/version /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-spring-boot3-starter/artifactId version3.5.7/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency /dependencies注意 Spring Boot 3.x 必须引入mybatis-plus-spring-boot3-starter旧版的mybatis-plus-boot-starter只适配 Spring Boot 2.x这是最常见的兼容错误。spring-boot-starter-parent已统一管理依赖版本所以 MySQL 驱动不需要写version。后端目录我按controller/service/mapper/entity四层拆分Controller 只做参数接收和返回Service 写业务事务Mapper 继承 MyBatis-Plus 的 BaseMapper。这个分层结构画成图可以直接放进论文的“系统设计”章节。3.2 领用申请审批与出库的事务实现核心功能分为两个操作申请创建和出库扣减。申请创建相对简单只插入一条PENDING记录最多做一个数量上限校验。真正容易错的是出库因为它要同时修改t_apply状态和t_item.stock。下面用一个Transactional方法完成。Service public class ApplyService { Resource private ApplyMapper applyMapper; Resource private ItemMapper itemMapper; Resource private ApprovalMapper approvalMapper; Transactional(rollbackFor Exception.class) public void outbound(Long applyId, Long approverId) { Apply apply applyMapper.selectById(applyId); if (apply null || !APPROVED.equals(apply.getStatus())) { throw new BusinessException(当前状态不能出库); } // 锁定库存行防止并发超发 Item item itemMapper.selectByIdForUpdate(apply.getItemId()); if (item.getStock() apply.getApplyCount()) { throw new BusinessException(库存不足); } item.setStock(item.getStock() - apply.getApplyCount()); itemMapper.updateById(item); apply.setStatus(OUTBOUND); applyMapper.updateById(apply); Approval approval new Approval(); approval.setApplyId(applyId); approval.setApproverId(approverId); approval.setAction(OUTBOUND); approvalMapper.insert(approval); } }核心是selectByIdForUpdate它对应 SQL 中的SELECT ... FOR UPDATE在事务内锁住该物品行第二个并发请求会等待直到第一个事务提交。Transactional保证item扣减、apply状态、approval记录要么全部成功要么全部回滚。selectByIdForUpdate需要在ItemMapper中自己定义Select(SELECT * FROM t_item WHERE id #{id} FOR UPDATE) Item selectByIdForUpdate(Long id);如果使用 JPA可以用Lock(LockModeType.PESSIMISTIC_WRITE)达到同样效果。还有一个常见错误把库存判断放在 Controller 或 Service 方法外面先selectById判断再调用outbound这样判断和扣减就不是原子操作并发时照样超发。所以库存判断和扣减必须在同一个事务方法内。3.3 压测验证与锁策略选择给这个接口做并发验证时我用 JMeter 创建 50 个线程同时对一个库存为 10 的物品提交申请每个申请数量 1。最终t_apply中OUTBOUND的记录数应该是 10库存为 0。如果出现第 11 条成功记录说明锁没有生效。不同锁策略的适用场景可以简单对比如下锁方式实现适合场景乐观锁 version 字段UPDATE t_item SET stock stock - 1 WHERE id ? AND stock 1冲突较少SELECT ... FOR UPDATE悲观行锁高并发扣库存Redis 分布式锁SET key value NX EX 10多实例部署第一版毕设系统用行锁最简单也最容易解释清楚。如果以后要拆成多个后端实例再把 Redis 分布式锁加上锁的 key 可以设计为item:lock:{itemId}。3.4 用 Redis 缓存库存列表降低数据库压力办公用品列表是首页高频查询但库存实时性要求并不高可以加一层 Redis 缓存。加入spring-boot-starter-data-redis后通过StringRedisTemplate手动实现缓存这样能更直观地控制缓存键和过期时间。Service public class ItemService { Resource private StringRedisTemplate stringRedisTemplate; Resource private ItemMapper itemMapper; public ListItem listItems() { String key item:list; String cached stringRedisTemplate.opsForValue().get(key); if (cached ! null) { return JSON.parseArray(cached, Item.class); } ListItem items itemMapper.selectList(null); stringRedisTemplate.opsForValue().set(key, JSON.toJSONString(items), 60, TimeUnit.SECONDS); return items; } }缓存过期时间不要设置为 1 秒或 1 小时前者起不到缓存效果后者会导致缺货了用户还在申请。我一般设置 30~60 秒并在出库时主动删除item:list缓存键这样下次查询能读到最新库存。3.5 多环境配置与参数管理Spring Boot 支持用application-{profile}.yml区分开发、测试、生产环境。在application.yml中指定激活项spring: profiles: active: profiles.activeprofiles.active是 Maven 构建时替换的占位符需要在pom.xml中定义 profile。开发环境application-dev.yml会打印 SQL 日志生产环境则关闭。这个配置过程也是springboot 配置里的高频考点。对于低库存预警阈值一类的自定义业务参数可以放在custom节点下custom: low-stock-threshold: 5再用ConfigurationProperties(prefix custom)读取这样论文里可以写“系统业务参数支持外部化配置”比硬编码在代码里更有说服力。4. 用 Vue 搭建前端页面路由、状态管理与打包布局处理4.1 使用 Vite 创建 Vue 3 项目并配置开发环境前端我使用 Vue 3 Vite Element Plus。创建项目的命令如下npm create vitelatest office-supply-web -- --template vue cd office-supply-web npm install npm install vue-router4 pinia axios element-plusNode 版本建议 18否则 Vite 会提示Unsupported engine。这是vue安装及环境配置最常见的坑先看 Node 版本再动手。如果npm install慢可以配置国内镜像速度会明显提升。项目创建后我会先清掉src/components/HelloWorld.vue然后按views、router、store、utils四个目录组织代码。4.2 开发代理与 Axios 统一封装Vite 开发服务器默认跑在 5173后端接口在 8080所以需要配置代理。在vite.config.js中设置export default defineConfig({ server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })后端所有接口都以/api开头这样前端请求/api/item/list会被转发到http://localhost:8080/api/item/list。changeOrigin必须为true否则后端收到的 Host 头还是前端域名个别请求会异常。Axios 封装时我会新建src/utils/request.jsimport axios from axios import { ElMessage } from element-plus const request axios.create({ baseURL: /api, timeout: 10000 }) request.interceptors.response.use( response response.data, error { if (error.response?.status 401) { // 放在单独的 auth.js 中避免和 router 文件循环依赖 window.location.href /login } else { ElMessage.error(error.response?.data?.message || 请求失败) } return Promise.reject(error) } ) export default request这里有一个容易踩的坑直接在拦截器里import router后调router.push可能因为循环依赖导致router是undefined。最简单的处理方式是window.location.href强制跳转或者把跳转逻辑放到一个单独模块中。4.3 用 Pinia 管理领用申请流程领用申请在页面上是多步操作选择物品、填写数量、提交。把当前申请单放在 Pinia store 中多个组件共享状态时不用层层传递 props。新建src/store/applyStore.jsimport { defineStore } from pinia export const useApplyStore defineStore(apply, { state: () ({ item: null, count: 1, submitting: false }), actions: { async submit() { this.submitting true try { const res await request.post(/apply, { itemId: this.item.id, applyCount: this.count }) return res } finally { this.submitting false } } } })Pinia 相比 Vuex 少了 mutations可以直接在 action 里改 state中小型管理系统开发效率高很多。注意在setup外使用useApplyStore()时必须先执行app.use(pinia)。否则会报getActivePinia was called with no active Pinia这个问题在 Vue 相关面试里也经常被拿出来问。4.4 路由模式与打包后布局异常的处理开发模式下一切正常但npm run build部署到 Nginx 后刷新页面会 404或者资源加载不出来。这要看路由模式hash 模式 URL 带#刷新不会请求后端history 模式 URL 没有#刷新时 Nginx 会去磁盘找真实文件找不到就 404。路由模式URL 示例部署要求hash/#/apply/list无特殊配置history/apply/listNginx try_files 回退到 index.html使用 history 模式时Nginx 配置这样写location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; }关于vue 打包后布局异常大部分是静态资源路径使用了绝对路径/assets/xxx.js部署到子路径后找不到资源。解决办法是在vite.config.js里设置base: ./让构建产出使用相对路径。这个参数很多人忽略评审演示时却会直接暴露。5. 用测试数据和部署记录让办公用品管理系统达到验收标准5.1 后端接口冒烟测试用 JUnit 验证事务回滚演示或答辩时最怕被问“怎么证明系统可靠”。与其口头描述不如写一个接口测试。我习惯用 Spring Boot 的 MockMvc 测核心流程先准备一条库存为 1 的物品提交申请数量 2断言接口返回“库存不足”并验证t_apply中没有新增记录。Test Transactional void stockNotEnoughShouldReject() throws Exception { mockMvc.perform(post(/api/apply) .param(itemId, 1) .param(applyCount, 2)) .andExpect(status().isOk()) .andExpect(jsonPath($.success).value(false)); }测试类加Transactional后测试结束数据自动回滚不会污染开发库。这个测试同时覆盖了业务校验和事务机制是性价比最高的验证手段。5.2 用 Docker Compose 做本地演示部署为了确保换一台电脑也能跑起来我会用 Docker Compose 一键拉起 MySQL、Redis、后端和前端。容器服务名直接作为主机名访问后端连接 MySQL 不能写localhost要写服务名mysql否则会报Communications link failure。部署后验证用两条 SQL 就够SELECT stock FROM t_item WHERE id 1; SELECT COUNT(*) FROM t_apply WHERE status OUTBOUND;5.3 并发验证的一个具体技巧在 JMeter 里模拟 50 个用户同时申请库存为 10 的物品时除了看响应时间更要在请求结束后查数据库确认库存数 OUTBOUND 记录数 初始库存。如果出现库存为负就要重点检查selectByIdForUpdate是否真的进入了事务方法以及 Service 调用链上有没有被非代理方法拦截。这个小测试能直接支撑论文里“系统支持并发库存扣减”的结论而不用凭空说“系统并发能力好”。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门