☰
SpringBoot+Vue供应商管理系统毕设项目实战解析
2026/9/30 8:26:03 网站建设 项目流程

毕设选项目这件事,我当年也是翻来覆去折腾了很久,最后敲定做供应商管理系统,技术栈直接用了 SpringBoot + Vue,数据库配的 MySQL。整套做下来我的体会非常深:这类企业管理系统的业务边界足够清晰,不像纯增删改查那样单薄,也不会像电商平台那样超出个人掌控范围,而且 SpringBoot、Vue、MySQL 这三样在企业开发里都是绝对主流,答辩时每个技术点都有得聊。

不过我也很清楚,很多同学搜到"SpringBoot+Vue 供应商管理系统管理平台源码【适合毕设/课设/学习】Java+MySQL"这种标题时,脑子里真正的问题是:解压之后我该看什么?先跑哪个?数据库脚本怎么导?答辩被问倒了怎么办?这篇文章我就围绕这套系统完整拆开讲,从业务设计、数据库表结构、后端接口与权限控制、前端页面组织,到从零跑通项目的每一步,以及我在实际调试和帮人改代码过程中踩过的坑,一次性给你讲透。

1. 供应商管理系统的业务闭环与模块边界

先别急着打开代码,你要先搞清楚这套系统到底在解决什么问题。供应商管理系统,本质上管的是采购环节的"人、货、钱"。现实里采购部门的痛点很典型:供应商信息散落在社交软件聊天记录和 Excel 表格里,换个采购员接手就是两眼一抹黑;同一个商品找谁买、什么价格,全靠老员工的经验记忆;合同什么时候到期没人提醒;供应商连续几次送货延迟,也没有数据化的方式把它暴露出来。系统的目标,就是把供应商从"准入"到"淘汰"的全生命周期管起来,让采购决策从"凭感觉"变成"看数据"。

我做的这套系统,功能模块分成六块:

  • 系统管理:用户、角色、菜单权限,支撑整个平台的登录和访问控制。
  • 供应商档案管理:供应商基础信息登记、资质文件维护、联系人信息,覆盖供应商准入到合作状态变更。
  • 产品管理:维护供应商提供的商品/服务目录,以及报价和价格变更记录。
  • 采购订单管理:从下单、供应商确认、发货到收货确认的完整状态流转。
  • 合同管理:合同台账登记、有效期管理,以及到期提醒。
  • 统计报表:采购金额趋势分析、供应商绩效评分排行。

模块划分不是拍脑袋,是有业务依据的。采购部门日常办公就是这些事:维护供应商、比价、下单、跟单、管合同、评价供应商。毕设答辩时老师经常问"你为什么这样划分模块",本质就是考察你是否理解业务,而不是为了凑工作量硬加功能。

还要说清楚一个边界问题:为什么不做库存管理?因为供应商管理系统和库存管理系统的业务边界完全不同——供应商系统管的是"采购环节",货物入库后的库存消耗是另一个系统的事。硬把库存塞进来,功能确实多了,但业务逻辑会变得混乱,演示的时候操作链路太长反而容易翻车。毕业设计项目,边界清晰比功能堆砌重要得多。

整条核心业务闭环是这样的:供应商提交档案或由管理员录入 → 资质审核通过后录入产品报价 → 采购方根据价格、交期、历史表现选择供应商下单 → 订单在多个状态间流转 → 收货验收后触发结算 → 最后根据这一单的质量、交期、价格等指标对供应商打分。从"选"到"评"正好覆盖了供应商管理的全部关键动作,这套闭环在你答辩讲业务流程时也是现成的叙事主线。

2. 技术栈选择的深层逻辑:为什么非这套不可

很多人的选型理由是"大家都用""网上说这个好",但答辩和面试真正考察的是你能否解释"为什么选它"。我把这套组合的逻辑梳理给你。

先看前后端分离架构。Vue 单独作为前端工程,SpringBoot 只提供 RESTful API,两者通过 JSON 通信。为什么要这么做?因为这是当前企业开发的标配模式,前端和后端可以独立开发、独立部署、独立扩展。毕设选它,展示面也广——你可以同时演示后端接口的规范性和前端交互的流畅性,技术深度比单体 JSP 项目高一大截。

再看 SpringBoot。它的核心价值是极大降低了 Spring 项目的搭建和配置成本。内嵌 Tomcat,打一个 jar 包就能直接跑起来,不需要单独装服务器;自动配置机制让数据库连接、MyBatis 集成这些工作从 XML 配置地狱里解放出来;依赖管理直接用 starter,引入一个依赖就带好整个生态。一个合格的 Java 学习者,上手 SpringBoot 是基本盘,做毕设时选它也是最稳妥的。

然后是 Vue。我选择 Vue 2 + Element UI 这套组合(如果你的版本是 Vue 3 + Element Plus,思路完全一致)。Vue 的学习曲线比 React 平缓,中文生态极其成熟,Element UI 把表格、表单、弹窗、分页、菜单这些管理系统高频组件都封装好了,前端工作量能砍掉一大半。Vue 的响应式数据绑定也让页面和状态的联动写得非常自然。

最后说 MySQL。关系型数据库里最经典的开源选择,事务支持可靠,安装维护简单,Navicat、DataGrip 这些可视化工具都很成熟,网上资料一抓一大把。供应商管理这种强结构化、强关联数据的场景,本质就是关系型数据库的主场,用户表关联角色、供应商关联产品关联订单,SQL 表达得非常清晰。

这套组合最核心的优势,我总结为三个"可控":运行环境可控(本地起两个服务就行)、代码量可控(一个学生能在两个月内写完并理解全部代码)、展示效果可控(前端界面漂亮、后端接口规范,答辩现场不会翻车)。选技术栈不是选最炫的,而是选你最能驾驭的,这一点在你决定拿这套源码做毕设的时候尤其重要。

3. 数据库设计:供应商管理的表结构与关键取舍

数据库是这套系统的地基,我建议你拿到源码后第一个打开的就是数据库设计文档或 SQL 脚本,把表和表的关系理清楚,后面看后端代码会非常快。

核心表我梳理成下面这张表,字段取关键项,方便你对照。

表名表用途核心字段
sys_user系统用户id, username, password, real_name, phone, role_id, status
sys_role角色表id, role_name, role_code(admin/purchaser/viewer)
supplier供应商主表id, supplier_code, company_name, legal_person, contact_name, contact_phone, address, status, level
qualification供应商资质表id, supplier_id, cert_name, cert_no, file_url, expire_time
product产品/服务表id, product_code, product_name, spec, unit, supplier_id, status
product_price产品报价表id, product_id, supplier_id, price, effective_time, expire_time
purchase_order采购订单主表id, order_no, purchaser_id, supplier_id, order_time, total_amount, status
order_item订单明细表id, order_id, product_id, product_name, quantity, unit_price, subtotal
contract合同表id, contract_no, supplier_id, contract_name, start_time, end_time, amount, file_url, status
evaluation供应商绩效表id, order_id, supplier_id, quality_score, delivery_score, price_score, total_score, evaluate_time, evaluator

这里面有几个设计取舍是答辩高频考点,我逐个说:

第一,为什么订单要拆主表和明细表?因为一个采购订单可能包含多个商品,用一张表存会把多组商品塞进一行,完全没法查询和统计。主表存订单整体信息(关联供应商、总金额、状态),明细表一行一个商品,通过 order_id 关联。这是典型的"一对多"建模,也对应了"一个主表对应多个明细"的标准范式。

第二,为什么价格单独建表而不是直接存在 product 表里?因为价格是会变的。如果直接把价格写在产品表,每次调价都会覆盖历史数据,后面做"某个期间内价格走势分析"就无从谈起。单独建 product_price,把生效时间和失效时间都记下来,就能追踪每一次调价。这个点你答辩时主动讲出来,老师会感觉你确实做过思考。

第三,状态字段统一用 tinyint 存枚举值,比如订单状态 0 表示待确认、1 表示备货中、2 表示已发货、3 表示已收货、4 表示已完成、5 表示已取消。用魔法数字不好维护,所以 Java 代码里要定义一个枚举类做映射。

第四,金额字段必须用 DECIMAL,比如 DECIMAL(10,2),绝对不要用 FLOAT 或 DOUBLE。浮点数在计算机里本身就是不精确的,涉及钱的运算一旦出现精度误差,演示时很尴尬。这一点属于那种"不翻一次车就不会长记性"的坑,我直接替你说在前面了。

第五,逻辑删除而不是物理删除。供应商录错想删除?物理 DELETE 会把关联的历史订单数据链搞得断掉,正确定位是加一个 status 或 is_deleted 字段,查询时默认过滤掉已删除数据。管理系统的"删除"在大多数时候都是"标记删除",这是行业惯例。

表设计这块,数量在 10 张左右是最舒服的量级——足够覆盖一个完整业务闭环,又不至于让数据库关系图密到讲不清楚。我见过有人一个毕设建了三十多张表,结果自己讲的时候都绕晕了,完全没必要。

4. 后端落地细节:分层、JWT鉴权与订单状态机

理解了数据库结构,你大概就能猜出后端 Service 层是怎么组织的了。我按实际工程结构给你拆一下。

后端包结构是标准的四层:controller 层只负责接收请求和参数校验;service 层写业务逻辑;mapper 层用 MyBatis 操作数据库;entity 和 dto/vo 分别对应数据库表和接口传输对象。另外还有 common 包放统一返回结果 Result、全局异常处理、JwtUtil 等工具类,config 包放拦截器、CORS 跨域配置。

先说统一返回结果。所有后端接口都返回同一个包装结构Result<T>,包含 code、msg、data 三个字段。这样前端 axios 拦截器只认这一种格式,错误处理极其统一。这是企业级接口设计的标准做法,也是答辩时你展示"工程意识"的第一个点。

然后是 JWT 鉴权。为什么用 JWT 而不是 Session?因为前后端分离架构下前端和后端不部署在同一台服务器,Session 依赖 Cookie 和服务端存储,天然不太适合;JWT 是无状态令牌,用户登录后后端签一个带签名和过期时间的 token 给前端,前端存起来,之后每次请求放在请求头里,后端验证签名就能确认身份。核心拦截器逻辑简化后长这样:

public class JwtInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 如果是预检请求,直接放行 if ("OPTIONS".equals(request.getMethod())) { return true; } String token = request.getHeader("Authorization"); if (token != null && token.startsWith("Bearer ")) { token = token.substring(7); Claims claims = JwtUtil.parseToken(token); // 把用户id放入request域,后续controller可以直接取 request.setAttribute("userId", claims.get("userId")); return true; } response.setStatus(HttpStatus.UNAUTHORIZED.value()); return false; } }

对应的登录接口流程是:接收用户名密码 → BCrypt 比对密码哈希 → 生成 token(把 userId、用户名、角色编码放进 claims)→ 返回给前端。前端拿到 token 存 localStorage,后续请求由请求拦截器统一附加到 Authorization 头。这套流程我在答辩时被问过很多次,你只要按"无状态 + 签名校验 + 前端存储 + 拦截器验证"这条线讲,基本稳了。

关于密码存储,多说一句:密码在数据库里绝不能明文存。我用的 BCrypt 哈希,同一个密码每次生成的哈希都不一样,即使数据库泄露也无法逆向。如果源码里用的是 MD5,建议你自己改成 BCrypt,这个点在答辩现场也很加分。

再说订单状态机。这是整个系统里业务逻辑最有含金量的部分。订单状态我设计成六个:待确认 → 备货中 → 已发货 → 已收货 → 已完成,任何一方都可以取消进已取消。每种状态下,前端渲染的操作按钮不同,后端接收的操作也有严格的合法性校验。比如处于"待确认"状态的订单只能供应商确认,不能直接做发货操作;被取消的订单不能再流转。这个限制写在哪?写在 Service 层,每次更新状态前先判断当前状态是否允许目标操作。我看过不少毕设项目,状态流转完全不设防,前端想点哪个按钮就点哪个按钮,后端也不校验,演示的时候一旦乱点就暴露了。状态机实现不复杂,却是区分"会写代码"和"会做系统"的重要标志。

最后给一个典型的分页查询接口示例,后端最常见的写法:

@RestController @RequestMapping("/api/supplier") public class SupplierController { @Resource private SupplierService supplierService; @GetMapping("/page") public Result<PageResult<SupplierVO>> page( @RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize, @RequestParam(required = false) String companyName, @RequestParam(required = false) Integer status) { return Result.ok(supplierService.pageQuery(pageNum, pageSize, companyName, status)); } }

分页查询用 MyBatis 的 PageHelper 插件,一行调用就能完成物理分页,这个插件是 SpringBoot + MyBatis 生态的标配,毕业设计里用得非常普遍。

5. 前端组织方式:页面骨架、请求封装与路由守卫

后端通了之后,前端的作用就是把这些接口一个个"挂"到页面上。我按前端工程的实际目录结构来讲,这样你看代码的时候有地图。

前端 src 目录下核心分层是这样的:api 目录按模块放接口请求文件,每个页面一个文件,比如 supplier.js 里就封装供应商相关的所有请求;router 目录配置路由和路由守卫;views 目录放页面组件,比如 SupplierList.vue、OrderList.vue、Dashboard.vue;store 目录用全局状态管理存用户信息,Vue 2 配 Vuex、Vue 3 配 Pinia 都行;utils/request.js 是 axios 的二次封装。

先说 axios 封装。为什么不能每个组件里直接调 axios?因为你要统一处理 token 附加、错误提示、401 跳转这些横切逻辑。封装之后,每个页面只需要这样调:

import request from '@/utils/request' export function pageSupplier(params) { return request({ url: '/supplier/page', method: 'get', params }) }

封装的 request.js 核心逻辑是这样:

import axios from 'axios' import { Message } from 'element-ui' import router from '@/router' const request = axios.create({ baseURL: '/api', timeout: 10000 }) request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = 'Bearer ' + token } return config }) request.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { Message.error(res.msg || '请求失败') return Promise.reject(new Error(res.msg)) } return res }, error => { if (error.response && error.response.status === 401) { localStorage.removeItem('token') router.push('/login') } Message.error(error.response && error.response.data ? error.response.data.msg : '网络异常') return Promise.reject(error) } ) export default request

这套封装把三件核心事情一次搞定:请求时带 token、响应时统一解包并弹错误提示、token 失效时自动跳登录页。前端架构的"工程化"主要体现在这里。

然后是路由守卫。未登录用户不能进系统页面,登录后角色不同看到的菜单也不同。路由守卫的典型写法:

router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.path !== '/login' && !token) { next('/login') } else { next() } })

菜单权限这块,我采用的是"后端返回当前用户角色可访问的菜单,前端动态渲染"的策略,而不是把菜单写死在页面里。具体实现是:登录后获取用户信息和角色编码,路由表里给菜单加上 meta.roles 字段,根据角色过滤后渲染侧边栏。这里有个小技巧:不要把只看权限的"查看者"角色和采购员混在一起,至少做区分,答辩时才好讲"权限控制"是怎么落地的。

然后看核心页面。供应商列表页我用的 Element UI 的 el-table 加 el-pagination,顶部是搜索表单(按公司名模糊查询、按下拉框筛状态),数据通过pageSupplier接口拉取。新增和编辑共用一个 el-dialog 弹窗表单,用 el-form 的 rules 做必填校验,提交成功后刷新列表。这个页面是整个系统最标准的 CRUD 示例,你看懂了它,产品管理、合同管理页面基本都能看懂。

订单管理页面比纯 CRUD 多一个东西:根据订单当前状态动态显示操作按钮。比如待确认状态显示"确认订单"和"取消",备货中状态显示"发货",已发货状态显示"收货",已完成状态显示"去评价"。这个设计跟后端的订单状态机一一对应,也是你要重点演示、重点讲解的页面。

数据统计页面用 ECharts,采购金额趋势图按月份展示订单总金额,供应商绩效排行用柱状图展示综合评分。图表这种东西在答辩现场特别抓眼球,而且 ECharts 的配置不复杂,花半天时间就能调出效果来,性价比极高。

6. 从源码到跑通:环境搭建与排错全过程

拿到源码之后,最急迫的事情就是"让它跑起来"。很多同学第一反应是直接双击,然后被数据库连不上、依赖下载失败、端口冲突轮番折磨。我给你一套我实测过的完整顺序,照着走基本一次成功。

先看环境清单。

软件版本建议说明
JDK1.8 或 更高版本SpringBoot 2.x 对应 JDK 8,SpringBoot 3.x 需要 JDK 17,看清楚源码的版本
Maven3.6+后端依赖管理和构建
MySQL5.7 或 8.0注意和驱动版本匹配
Node.js14 或 更高版本前端构建和依赖安装
IDEIntelliJ IDEA + VSCode / WebStorm后端用 IDEA,前端随意
Navicat 或 DataGrip任意数据库可视化管理

后端启动步骤:

  1. 用 IDEA 导入后端项目,等 Maven 下载完依赖。国内网络建议给 Maven 配阿里云镜像,否则下载时间可能以小时计。
  2. 打开 Navicat 新建数据库,字符集选 utf8mb4,然后导入项目 SQL 脚本。SQL 文件一般放在根目录或 sql 目录下,文件名类似 init.sql 或 schema.sql。
  3. 修改 application.yml 里的数据库账号密码,改成你自己本地的配置。
  4. 运行启动类。启动类一般在某个具体包路径下,类名大致长这样:SupplierApplication 或 DemoApplication。看到 "Started ... in x seconds" 就是成功了。

前端启动步骤:

  1. VSCode 打开前端项目,确认存在 package.json。
  2. 在终端执行依赖安装,国内环境建议走国内镜像源。
  3. 安装完成后执行开发服务器启动命令。启动成功的标志是终端输出 Local 访问地址。
  4. 浏览器打开本地地址,先用初始账号登录。

下面是几个高频报错的排查清单,我把我实际见过的原因和处理方式列在一起:

报错现象根本原因处理方法
启动时报 Access denied for user数据库账号或密码不对,或 MySQL 用户不允许远程连接核对 application.yml 配置,本地连 localhost 即可
报 Public Key Retrieval is not allowedMySQL 8 的驱动认证问题数据库连接串加 allowPublicKeyRetrieval=true
报 Communications link failureMySQL 服务没起来或端口不是 3306检查 MySQL 服务状态和端口配置
后端启动端口被占用8080 或自定义端口已被其他程序占用找出占用进程结束,或修改 application.yml 端口
前端页面请求接口 404前后端没有同时启动,或接口路径对不上确认后端已启动,确认前端请求和后端 RequestMapping 一致
页面数据接口报跨域后端没有配置 CORS 或配置不生效后端配置跨域放行,注意前端走本地代理时排查 vite.config 或 vue.config
前端依赖安装卡在某个包不动网络问题依赖下载失败清理 npm 缓存换国内镜像重装

跨域问题我要单独展开说。前后端分离后,前端运行在 8080(或 5173),后端运行在 8081(或 9090),端口不同就叫跨域。解决方式有两种:后端加 CORS 配置类统一放行;或者前端开发时用代理转发,把/api开头的请求转发到后端真实地址。我在项目里两个都做了,后端 CORS 配置保证任意场景可用,前端代理让本地开发更自然。两套都写上,答辩时可以把"为什么同时配置两种"这个问题也答得很好。

数据库脚本导入这里还有一个常见坑:SQL 文件里如果带了创建数据库的语句,你要先看清楚数据库名字,别导入到错误的库;如果带了外键约束,导入顺序错了会报外键缺失,直接用 Navicat"运行 SQL 文件"通常能处理顺序问题。我见过有人把整个 SQL 内容复制到查询窗口执行,表一多执行到一半中断,结果留下一堆脏数据,最后只能重新导入。

7. 让源码真正变成"你自己的项目":改造与答辩经验

跑通只是第一步。很多人拿了源码跑通之后就觉得任务完成,跑去答辩,结果老师问"这个查询条件在哪里加的"都答不上来,现场的尴尬程度我见得太多了。源码可以借鉴,但你不能让它底层就是"黑盒"。

先说拿到源码后的正确读码顺序。我第一次拿到完整项目也是有点无从下手,后来总结出一套三步法:第一步先跑通,验证整体没问题;第二步从数据库入手理业务,表结构看懂了,代码大概猜得出来;第三步挑一条完整链路读代码,比如"供应商新增"这个过程从前端点击按钮到后端写库,一条线通读一遍。一条线读懂了,其他页面都是这个套路。这套方法你拿去用,一下午就能对项目建立起整体认知。

然后是改造方向。直接交原封不动的源码,出事概率极高。至少要做的改造是这几类:第一,全局改项目名和包名,很多源码的包名是 com.demo 或者干脆就叫 demo,改成你自己的命名是基本操作;第二,给业务代码补注释,不需要每行都写,但在 Controller、Service 的关键方法上面写清楚"这个方法干什么、用什么逻辑实现",老师在代码里看到注释,印象分会好很多;第三,加一个源码里没有的小功能,哪怕只是导出 Excel 或加一个统计图表,都是你"有独立开发能力"的证据。

我建议的扩展方向按工作量从小到大排列:

  • 增加供应商 Excel 导入导出,用 Apache POI 操作,这是管理系统最常见的需求,代码量不大但很实用。
  • 增加合同到期邮件提醒,用 Spring Task 定时扫描合同表,提前提醒即将到期或已过期合同。
  • 增加文件上传功能,把供应商资质、合同附件通过本地或 MinIO 存起来,前端用 el-upload 组件就好。
  • 给热点数据加 Redis 缓存,比如供应商档案、产品基础信息,能明显改善查询性能,也给你的技术栈加一层。
  • 引入 ECharts 做更丰富的统计,比如供应商地区分布地图、订单完成率环形图,视觉冲击力很强。

答辩时的高频问题,我帮你先过一遍:为什么选 JWT 不用 Session?为什么订单要拆主表和明细表?状态流转在哪里校验合法性?数据库为什么用逻辑删除?密码为什么用 BCrypt?价格为什么要单独建表?这些问题的答案我在前面各章都点到了,你只要用自己的话讲清楚"业务需要什么、我的实现对应做了什么"就足够。千万不要背名词,老师追问两轮就会露馅。

还有一个答辩演示的小技巧:演示的时候提前把测试数据准备好,供应商至少录 10 个,产品 20 个以上,订单状态分布要覆盖"待确认、备货中、已完成"至少三种状态,合同一定要有一条快到期的记录。这样演示时点一个页面就有数据展示,不用现场录数据浪费时间,也更有利于把节奏掌握在你自己手里。

我最后想说的是,这套 SpringBoot + Vue 的供应商管理系统选型确实很适合毕设和课设,但真正的价值不在于"跑起来",而在于你通过它把前后端分离架构、数据库设计、接口规范、权限控制这些核心概念完整走了一遍。源码给你节省的是搭骨架的时间,阅读代码和动手改造的能力,谁也替代不了。按上面这个顺序一步步来,答辩的时候你会比想象中从容很多。

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

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

立即咨询