新闻详情

新闻详情

首页 / 资讯中心 / 详情

Spring Boot + Vue 食品公司采购管理系统全栈开发与部署实战

发布时间:2026/10/2 4:58:40来源:尧图网络
Spring Boot + Vue 食品公司采购管理系统全栈开发与部署实战
“东方红食品公司采购管理系统”这个名字听起来挺像学生在毕业设计里会选的项目但实际上它的业务骨架非常典型。食品行业做采购跟普通贸易公司完全不一样原材料保质期短、供应商资质要按批次核验、价格波动大、采购审批链条长这些痛点都会直接体现在系统设计里。Spring Boot Vue 这套组合做这类管理系统属于当前的主流方案后端负责业务逻辑和数据前端负责交互和展示前后端分离之后采购、库存、财务几块业务可以并行开发后续接 ERP 或财务软件也方便。这篇博客我会把这个项目从设计思路、核心代码、本地部署到生产环境上线完整拆一遍最后再分享一些我实际调试这类系统时踩过的坑。如果你想拿这个项目做毕业设计或者学习全栈开发这篇内容可以直接照着复现。1. 项目整体设计与需求拆解1.1 食品公司采购业务的核心痛点很多同学拿到“XX管理系统”的题目第一反应就是做增删改查。但食品公司采购管理系统的难点不在 CRUD而在业务流程的连贯性。食品企业采购原材料的场景往往是采购员根据生产计划提出申请部门主管审批采购经理复核然后采购员下单给供应商供应商送货后仓管员验收入库最后财务根据入库单和采购合同做应付账款。整个过程是典型的多角色、多状态流程。另一个痛点是供应商管理。食品行业对供应商资质要求很高营业执照、食品生产许可证、质检报告这些证件都有有效期系统里得有记录和到期提醒。原材料批次信息也需要留痕一旦出质量问题要能追溯到具体供应商和采购批次。这些需求就不是单纯一张表能搞定的需要合理设计状态字段和关联关系。1.2 系统模块拆解这个项目按照采购业务流我建议划分为这样几个模块模块核心功能涉及角色供应商管理供应商档案、资质证件、评级、历史合作记录采购员、采购经理采购申请管理创建申请单、多级审批、状态流转采购员、部门主管、采购经理采购订单管理订单生成、确认、发送供应商、收货确认采购员、仓管员入库管理到货验收、批次登记、库存联动仓管员应付款管理对账、付款登记、账期管理财务人员系统管理用户、角色、菜单权限管理员其中采购申请到采购订单这两个环节最核心。申请单通过审批后系统自动生成采购订单这个设计能有效防止采购员跳过审批直接下单。入库时库存表联动增加同时生成采购入库流水财务对账时直接按流水来避免月底手工对账。1.3 为什么选 Spring Boot Vue 前后端分离前后端分离是当前做企业级管理系统的标配方案。后端专注提供 RESTful API前端专注页面渲染和数据展示两者通过 JSON 交互团队可以并行开发。对于学习项目分离架构还有一个好处前端只需要一个静态服务器就能跑后端接口单独调试可以用 Postman 或 Apifox问题定位快。Spring Boot 的优势在于自动配置和生态成熟。Java 做管理系统事务管理、权限框架、代码生成的支撑都非常完善。Vue 这边用 Element UI 或者 Element Plus 做后台管理界面效率极高表格、表单、弹窗这些组件都是现成的配合 Vue Router 做路由控制Vuex 或 Pinia 做状态管理几天就能把管理后台的界面框架搭起来。2. 核心技术栈与关键代码解析2.1 后端技术选型与项目结构这个项目的后端我建议采用 Spring Boot 2.7.x 版本搭配 MyBatis-Plus。Spring Boot 3.x 虽然已经出了很久但很多企业项目还在用 2.x原因很现实生态兼容性问题。比如老牌的 Shiro、部分数据库驱动和生成器插件在 3.x 上需要额外适配。做管理类系统稳定性优先2.7 是我个人推荐的选择。数据库用 MySQL 8.0关系型数据库处理这类结构化业务数据最合适。ORM 层选 MyBatis-Plus它既保留了 MyBatis 的 SQL 灵活性又提供了 BaseMapper 通用接口单表 CRUD 不用写 SQL多表查询自己定义 XML 或注解 SQL。代码生成器可以用 MyBatis-Plus 自带的 Generator一键生成 entity、mapper、service、controller 四层代码省去大量重复劳动。项目标准结构建议这样分参考了阿里巴巴 Java 开发手册的约定src/main/java/com/dfh/purchase/ ├── config/ // 配置类跨域、拦截器、Swagger等 ├── controller/ // 接口层接收请求返回结果 ├── service/ // 业务层事务、业务逻辑 ├── mapper/ // 数据访问层MyBatis-Plus接口 ├── entity/ // 数据库实体类 ├── dto/ // 数据传输对象接收前端参数、返回前端数据 ├── vo/ // 视图对象聚合查询结果 ├── common/ // 公共类返回结果封装、异常处理、工具类 └── security/ // 权限认证组件每个实体对应一张表表结构设计时注意几个关键字段每个业务表都保留create_time、update_time、create_by、update_by这几个审计字段排查问题时候特别有用。状态字段用status或state用数字表示不同状态比字符串省空间也方便条件查询。2.2 前端核心设计与接口封装前端使用 Vue 2 Element UI 还是 Vue 3 Element Plus取决于项目实际情况。如果参考的模板是 Vue 2 的直接沿用 Vue 2 是性价比最高的选择。Vue 3 的 Composition API 写起来更现代但很多现成的后台模板、组件库生态还需要时间验证。我这里按 Vue 2 Element UI 讲因为大多数课程设计和毕业设计用的还是这套组合。前端目录结构同样关键src/ ├── api/ // 接口请求模块按业务拆文件 ├── assets/ // 静态资源 ├── components/ // 公共组件 ├── router/ // 路由配置含动态路由、路由守卫 ├── store/ // 状态管理Vuex的modules ├── views/ // 页面组件按模块建文件夹 └── utils/ // 工具函数axios封装等Axios 必须做统一封装。我见过不少项目每个页面单独写 axios 请求token 处理、错误提示、响应拦截全乱套。统一封装的核心逻辑是请求拦截器里从 localStorage 取 token放到请求头响应拦截器里根据后端返回的 code 字段统一处理成功和失败401 状态直接跳登录页。一个比较精简的 axios 封装是这样子的import axios from axios import { Message } from element-ui import router from /router const service axios.create({ baseURL: process.env.VUE_APP_BASE_API, timeout: 10000 }) // 请求拦截器 service.interceptors.request.use( config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] token } return config }, error Promise.reject(error) ) // 响应拦截器 service.interceptors.response.use( response { const res response.data if (res.code ! 200) { Message.error(res.message || 请求失败) if (res.code 401) { localStorage.removeItem(token) router.push(/login) } return Promise.reject(new Error(res.message)) } return res }, error { Message.error(error.message || 网络异常) return Promise.reject(error) } ) export default service2.3 采购单状态流转的设计与实现采购申请单的状态流转是业务系统的核心逻辑。我建议用数字枚举来控制每个状态对应数字标识业务方法里通过状态判断来保证流程合法状态值含义0草稿1待部门主管审批2待采购经理审批3审批通过待生成订单4已生成订单5已驳回这个流程在 Service 层实现关键点在于状态变更必须和品目行项目在同一个事务里完成。比如“提交审批”方法需要更新申请单状态为 1同时记录审批日志。日志表单独建一张字段包括申请单 ID、操作人、操作动作、操作时间、备注。这方便追溯整个采购申请的审批历史出了问题能查到是哪一环、哪个人、什么时候处理的。具体代码层面Service 层这样处理状态流转Service Transactional(rollbackFor Exception.class) public class PurchaseApplyServiceImpl extends ServiceImplPurchaseApplyMapper, PurchaseApply implements PurchaseApplyService { Override public boolean submitApply(PurchaseApplyDTO dto) { // 幂等校验只有草稿状态才能提交审批 PurchaseApply apply this.getById(dto.getId()); if (apply null || apply.getStatus() ! 0) { throw new BusinessException(当前状态不可提交); } apply.setStatus(1); apply.setSubmitTime(new Date()); this.updateById(apply); // 记录审批日志 ApprovalLog log new ApprovalLog(); log.setApplyId(apply.getId()); log.setAction(提交); log.setOperator(SecurityUtils.getCurrentUserId()); approvalLogService.save(log); return true; } }这里用了Transactional注解确保状态更新和日志写入要么都成功要么都失败不会出现状态变了但日志没记录的情况。SecurityUtils.getCurrentUserId()从当前请求上下文中获取操作人信息这在后边的权限管理模块会讲到。2.4 权限设计与拦截器实现管理系统必须考虑权限否则任何普通员工都能审批采购单就麻烦了。整体思路用 RBAC基于角色的访问控制模型用户属于角色角色拥有菜单权限。后端在登录成功后返回该用户的角色和权限标识前端根据权限标识控制菜单显示和按钮可见性。后端拦截器实现认证建议用 JWT 无状态方案。用户登录成功后生成 token 返回前端前端后续请求带上 token后端通过拦截器解析 token 拿到用户身份。Spring Boot 中可以用 HandlerInterceptorComponent public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行登录接口 if (request.getRequestURI().contains(/login)) { return true; } String token request.getHeader(Authorization); // 解析token失败则返回401 try { Claims claims JwtUtil.parseToken(token); request.setAttribute(userId, claims.get(userId)); request.setAttribute(role, claims.get(role)); return true; } catch (Exception e) { response.setStatus(401); response.setContentType(application/json;charsetUTF-8); response.getWriter().write({\code\:401,\message\:\未登录或登录已过期\}); return false; } } }拦截器配置在 WebMvcConfigurer 中指定放行路径和拦截路径。前端路由守卫则是另一层控制没登录访问受限页面直接重定向到登录页。前端路由守卫router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.path /login) { next() } else { if (!token) { next(/login) } else if (to.meta.roles to.meta.roles.indexOf(store.getters.role) -1) { next(/403) } else { next() } } })前后端双重校验后端保证数据安全前端保证用户体验这个设计思路一定要理解透。2.5 Excel 导入导出与批量操作采购管理系统里供应商清单、采购物品明细这些数据用户往往手里有现成的 Excel 表格如果入库靠手工录入效率极低。用 EasyExcel 工具库处理后端导入导出功能是目前的主流方案稳定性比 Apache POI 直接操作高很多内存占用也低。批量导入供应商的代码思路是这样的ApiOperation(导入供应商Excel) PostMapping(/import) public RString importSupplier(RequestParam(file) MultipartFile file) throws IOException { ListSupplierImportRow list EasyExcel.read(file.getInputStream()) .head(SupplierImportRow.class) .sheet() .doReadSync(); ListSupplier suppliers list.stream().map(row - { Supplier s new Supplier(); BeanUtils.copyProperties(row, s); return s; }).collect(Collectors.toList()); supplierService.saveBatch(suppliers); return R.ok(成功导入 suppliers.size() 条数据); }这里注意一个细节导入前一定要做数据校验。Excel 里可能有多余空行、重复数据、日期格式不对等建议在读取之后先校验再批量写入。实现校验时可以自定义一个导入模板在实体类的 ExcelProperty 注解上加上下拉校验比如状态列限制只能填“启用/停用”、日期列设置固定格式这样用户填错时 Excel 直接提示比后端做一堆校验判断更省事。3. 本地部署流程与运行指南3.1 环境准备清单在本地把项目跑起来并不复杂但环境版本一定要选对版本不对会折腾你大半天。依赖项推荐版本注意事项JDKJDK 1.8 或 JDK 11Spring Boot 2.7 兼容Maven3.6.3 及以上配好阿里云镜像加速Node.js14.x 或 16.xVue 2 Element UI 建议 14/16不要用 18 以上MySQL5.7 或 8.08.0 注意驱动和时区配置IDEIntelliJ IDEA安装 Lombok 插件Node.js 版本这个坑要单独说。如果你用的是 Vue 2 的老项目Node 18 以上运行npm install常常会报错要么 node-sass 编译失败要么依赖版本不兼容。最稳妥的办法是用 nvm 切换 Node 版本项目根目录放一个.nvmrc文件内容写lts/fermium大家切换到 Node 14 环境基本不会踩到 node-sass 的坑。3.2 后端启动步骤拿到源码后首先修改数据库配置。打开src/main/resources/application.yml核心配置如下server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/dfh_purchase?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: id-type: autoserverTimezoneAsia/Shanghai这个是 MySQL 8.0 必须加的参数不加会报时区错误。map-underscore-to-camel-case保证数据库字段create_time能自动映射到 Java 属性createTime。接下来执行src/main/resources/sql/init.sql初始化数据库这一步创建表结构和初始数据。初始数据很重要否则系统连登录账号都没有。一般会内置一个 admin 用户。启动类直接运行 main 方法即可。3.3 前端启动步骤后端跑起来之后进入前端目录执行命令# 安装依赖建议用cnpm或者配置镜像 npm install # 启动开发服务器默认端口8081 npm run dev前端开发服务器的跨域问题必须在前端配置里处理。项目根目录创建vue.config.jsmodule.exports { devServer: { port: 8081, proxy: { /api: { target: http://localhost:8080, changeOrigin: true, pathRewrite: { ^/api: /api } } } } }开发环境通过代理解决跨域生产环境则用 Nginx 反向代理。这里有个细节如果前端请求路径是/api/user/login后端接口路径是/api/user/loginproxy 不用重写如果后端接口没有/api前缀才需要 pathRewrite。配置代理时先搞清楚需求路径规则别上来就抄网上配置路径不对排查半天。3.4 前后端联调检查清单本地跑起来之后按照这个清单逐项验证能节省大量排错时间浏览器输入http://localhost:8081/login页面能打开输入初始账号密码登录请求能在 Network 面板看到后端 console 有 SQL 输出说明数据库连接正常登录成功后能跳到首页菜单权限按角色渲染打开供应商管理页列表能加载出数据新建一条采购申请单提交审批状态流转符合预期如果你按这个清单走完发现第 2 步登录接口返回 500多半是数据库没初始化成功仔细检查 Mapper 对应的表是否存在。如果返回 401说明 token 解析有问题重点看拦截器代码。4. 生产环境部署方案4.1 后端打包与 Linux 部署本地开发验证通过之后上生产环境又是另外一回事。后端打包很简单# 跳过测试打包jar mvn clean package -DskipTests打出来的 jar 包在target/dfh-purchase.jar。Linux 服务器上必须有 JDK 环境用java -jar dfh-purchase.jar前台运行只适合临时验证正式环境建议用 systemd 做成守护进程这样kill之后进程能自动重启# /etc/systemd/system/dfh-purchase.service [Unit] DescriptionDFH Purchase Management System Afternetwork.target mysqld.service [Service] Typesimple WorkingDirectory/opt/dfh-purchase ExecStart/usr/bin/java -Xms512m -Xmx1024m -jar /opt/dfh-purchase/dfh-purchase.jar Restartalways RestartSec10 Userroot [Install] WantedBymulti-user.target启动命令systemctl daemon-reload systemctl enable dfh-purchase systemctl start dfh-purchase systemctl status dfh-purchase生产环境的 MySQL 密码不要明文写在 application.yml 里提交到 Git。用环境变量方式覆盖配置application.yml里写${MYSQL_PASSWORD:}然后在 systemd 配置里通过 Environment 注入或者使用 Spring Boot 的--spring.datasource.passwordxxx启动参数。这是一个很小但很重要的安全意识。4.2 前端打包与 Nginx 配置前端打包命令npm run build打包产物在dist目录下。把这个目录复制到服务器/opt/dfh-purchase-web然后配置 Nginxserver { listen 80; server_name buy.dfh.com; # 前端静态资源 root /opt/dfh-purchase-web; index index.html; # 反向代理后端接口 location /api { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # Vue Router history模式需要配置 location / { try_files $uri $uri/ /index.html; } }try_files $uri $uri/ /index.html;这一行非常关键。Vue Router 默认使用 history 模式直接访问http://buy.dfh.com/supplier时服务器找不到对应的物理文件如果没有 try_files 会返回 404加上这行配置就会回退到 index.html然后由前端路由接手处理。4.3 数据库初始化与定期备份生产环境数据库初始化不能直接用本地开发的 init.sql 盲目执行。先在生产 MySQL 上创建独立账号分配最小权限只给该账号当前库的增删改查权限。然后用 mysql 命令导入表结构mysql -u dfh_admin -p -h 127.0.0.1 dfh_purchase init.sql定期备份是管理系统的最后一道防线。每天凌晨 2 点执行 mysqldump 备份保留最近 7 天备份文件写到 crontab 里0 2 * * * mysqldump -u dfh_admin -p密码 dfh_purchase /backup/dfh_purchase_$(date \%Y\%m\%d).sql备份文件建议保留在独立磁盘或者对象存储中不要和数据库放在同一个机器上防止磁盘损坏导致备份一起丢失。5. 常见问题与排查技巧实录5.1 高频问题速查表下面这些异常情况是我在部署和调试多个同类项目时反复遇到的问题整理成表格你可以直接对照排查。异常现象可能原因解决方案登录接口返回 500数据库连接失败或表不存在检查 MySQL 账号密码、IP 白名单、serverTimezone登录成功但页面空白前端路由守卫跳转逻辑问题看浏览器控制台报错检查 permission 相关逻辑列表数据加载缓慢SQL 未走索引检查 where 条件的字段是否建立索引上传的 Excel 文件解析中文乱码文件编码不是 UTF-8指定读取编码EasyExcel 增加编码参数跨域报错 No Access-Control-Allow-Origin生产环境没配置代理用 Nginx 反向代理解决不要在代码里放开所有跨域Vue 页面刷新 404Nginx 缺少 try_files添加try_files $uri $uri/ /index.html;5.2 排查思路与调试工具遇到问题别急着改代码先定位问题在哪一层。后端问题用 Postman 或 Apifox 直接调接口看返回 JSON 状态码和错误信息排除前端干扰。如果是接口返回数据格式不对优先看后端代码如果是接口正常但页面渲染不出来看前端 Console 报错和 Network 面板的响应。MyBatis-Plus 的 SQL 输出习惯很重要。开发环境把log-impl配置成 StdOutImpl控制台能看到执行的 SQL 和参数这比瞎猜 SQL 错误快得多。生产环境就关掉日志打印避免 SQL 里的敏感数据刷屏。5.3 我可以给到的几条经验心得做这种管理系统项目我最大的体会是状态设计和日志设计一定要提前做。很多同学原型都没画完就急着写代码写着写着发现采购申请单没有“撤销”操作又回头改表结构。表结构一改前端组件、接口文档、测试用例全都要跟着改代价成倍增长。我的建议是无论如何先用思维导图把每个模块的状态机画清楚再设计数据库最后才动代码。另一个经验是安全细节不要忽略。JWT 的密钥不要硬编码在代码里数据库密码不要提交到 Git这些习惯在工作之后非常重要。还有接口返回的信息不要太详细避免直接把 SQL 异常堆栈抛给前端这既是安全考虑也是用户体验考虑。最后说一句跟项目本身关系不大但每个开发都会遇到的问题代码注释。这个项目的源码讲解文档里我特别建议大家把每个状态值用枚举或常量类维护起来而不是散落在业务代码里写魔法数字。如果哪天审批流程加了一个“财务复核”节点聪明的注释和常量定义能让你的改动成本降低一半以上。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

5G NR中pi/2 BPSK与QPSK调制原理及实测星座图解析 2026/10/2 5:50:33

5G NR中pi/2 BPSK与QPSK调制原理及实测星座图解析

1. 这不是教科书里的抽象图,而是5G NR物理层实测信号的“指纹”你打开示波器或频谱仪,看到的不是教科书上那个完美对称、四点均匀分布的QPSK星座图——它可能歪斜、抖动、边缘模糊,甚至在I/Q平面上拉出一条条细长的“尾巴”。而当你把同样的设…

阅读更多 →
MCP+A2A组合实战:多智能体系统生产级部署的坑与解法 2026/10/2 5:50:27

MCP+A2A组合实战:多智能体系统生产级部署的坑与解法

先说个背景:这是《使用MCP和A2A设计多智能体AI系统》的第六篇。前几篇我们把MCP Server从零搭到了能跑,也把A2A协议里的AgentCard、Task模型、消息格式逐条拆过。但说实话,“能跑”和“能用在生产环境”之间,隔着的不是协议文档&a…

阅读更多 →
当一切交由 Agent,用户不再打开你的后台,SaaS 还值钱吗 2026/10/2 5:50:27

当一切交由 Agent,用户不再打开你的后台,SaaS 还值钱吗

1. 一个正在发生的静默转变过去半年,我跟不少做 SaaS 的朋友聊天,话题总会绕到同一个焦虑上:客户越来越不爱打开我们的后台了。以前产品经理天天盯着 DAU、页面停留时长、功能点击率,现在这些指标在很多场景下正在集体失效。原因不…

阅读更多 →
Element必填星号不显示?表单校验规则与prop配置详解 2026/10/2 5:50:27

Element必填星号不显示?表单校验规则与prop配置详解

1. 为什么表单校验总在"必填"这一关翻车做过后台管理系统的前端,十有八九都被"必填校验"折腾过。不是字段没配规则,而是用户根本不知道这个字段要填——等提交的时候被拦截,弹出一串红字,体验已经扣分了。真正…

阅读更多 →
开源模块化模拟驾驶支架DIY:铝型材搭建与调校全记录 2026/10/2 5:50:27

开源模块化模拟驾驶支架DIY:铝型材搭建与调校全记录

折腾装备这件事,我一直有个执念:能自己搭的,绝不买成品。前阵子想上一套模拟驾驶支架,看了一眼成品价格,再看了一眼自己钱包,果断决定走开源方案。最后相中了openrig——一套以开源图纸为核心的模块化装备机…

阅读更多 →
Flex布局本质:现代CSS空间契约与动态排版原理 2026/10/2 5:50:27

Flex布局本质:现代CSS空间契约与动态排版原理

1. 为什么 Flex 布局不是“又一种布局方式”,而是现代网页排版的底层操作系统你有没有试过用float实现一个三栏等高布局,最后发现右边栏被挤到下一行,调试了两小时才发现漏写了一个clear: both?或者用position: absolute把按钮硬塞…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉