☰
SpringBoot3+Vue3进销存系统毕设全攻略:从零搭建到答辩
2026/10/13 3:43:55 网站建设 项目流程

不知道你有没有发现,每年到了四五月份,跳出来的毕业设计求助帖一大半都是同一个问题:我是零基础,有没有一个不难上手、能写论文、能答辩的题目?如果你正好也在纠结这个,我建议你把目光放在进销存管理系统上,技术选型直接用 SpringBoot3 + Vue3。这个组合我已经带过好几轮同学从零完整跑通,从环境搭建到项目部署、论文编写、答辩准备,踩过的坑基本上都被我摸清楚了。这篇内容就是把这套进销存毕业设计项目的完整骨架、数据库设计、核心业务代码、前端页面逻辑和排坑清单一次讲透。

这套教程对应的交付物包括图文教程、可运行的源码、初始化数据库脚本、毕业设计文档和答疑服务。你对它的理解可以先简单记成一句话:它是专门帮零基础同学把“一个能跑、能讲、能扩展的毕设项目”从无到有做出来的完整方案。适合谁?适合没有完整做过前后端项目的本科生,也适合想快速补齐全栈开发经验的在职读者。下面我按实际开发顺序来讲,建议你边看边动手。

1. 为什么选中“进销存”这个选题:毕业设计里的黄金结构

1.1 业务规模不大不小,刚好是一份合格的毕设

先说说为什么是进销存。市面上的毕设题目大致可以分成两类,一类太简单,比如“图书管理系统”,做出来就是单表的增删改查,答辩时老师一问“你的业务复杂度在哪里”,自己先心虚;另一类太复杂,比如“电商平台”或者“ERP系统”,涉及订单状态机、支付、物流、优惠券,零基础一个月根本做不完,写论文的时候光业务流程图就能把人绕晕。

进销存正好卡在中间。它的核心链路非常清晰:采购商品入库,商品进入库存;销售商品出库,商品离开库存;随时可以查库存总量和流水明细。这个过程中涉及三个最核心的关系:商品、供应商、客户,再延伸出采购单、销售单、库存流水和统计报表。任何一个懂点业务的人,哪怕没写过代码,也能理解这套逻辑。这就是毕设选题里最难得的“业务直观性”,因为论文里面所有的功能模块图、业务流程图、数据流图都能顺理成章画出来,答辩的时候也不会卡壳。

从开发量看,进销存系统通常包含用户登录、商品管理、供应商管理、客户管理、采购入库、销售出库、库存查询、统计报表这几个模块。每个模块都是标准的前后端交互,没有特别偏门的算法,也没有实时通信之类的高难度要求。零基础同学跟着教程做,大概三到四周就能把一个完整项目跑起来,剩下两周用来改功能和写文档,节奏刚刚好。

1.2 技术组合的选型逻辑:SpringBoot3 + Vue3 为什么是当下标配

再解释一下技术选型。我推荐 SpringBoot3 + Vue3,不是因为它们最“新”,而是因为它们代表当前主流中小型项目的真实搭配,同时也是简历上最容易得到认可的组合。

先说 SpringBoot3。它最明显的变化是强制要求 JDK17 及以上版本,相比老项目常用的 JDK8,启动更快、内存管理更好,而且 Spring 生态的新组件基本都会优先兼容3.x。很多同学担心 JDK17 不熟悉,其实日常写接口、操作数据库,跟 JDK8 体验几乎一样,真正要注意的只是安装版本时别装错。

前端选 Vue3 而不是 Vue2,核心原因是 Vue3 配合 Vite 开发服务器,冷启动速度和热更新比老式 Webpack 方案快出好几个数量级。做毕设时经常需要反复改页面、刷新看效果,体验差别非常明显。再加上现在 Element Plus 组件库全面支持 Vue3,页面做出来比手写 CSS 美观得多,也不容易在答辩时被吐槽界面丑。

有人可能会问,用 SpringBoot2 + Vue2 不也挺稳定吗?确实稳定,但我的建议是不要选老版本。原因很简单:你现在学的是要写进简历里的技术,如果用一套两三年后会被淘汰的组合,投入产出比太低了。SpringBoot3 + Vue3 现在资料多、社区活跃,遇到问题基本都能搜到答案,对零基础来说反而是最省心的选择。

下面用一个简单的对比表看下常见的毕业设计技术路线差异:

技术路线学习成本简历认可度参考资料数量适合人群
JSP + Servlet + MySQL低低较多但偏旧只想快速交差,不打算找开发工作
SpringBoot2 + Vue2中中很多但逐渐过时基础较弱、时间紧张的同学
SpringBoot3 + Vue3中高高多且持续增长零基础但愿意投入时间的同学

2. 系统功能规划与数据库设计:先想清楚再动手

2.1 功能模块拆解:让每一步开发都有明确目标

很多新手犯的错误是一拿到源码就开始看代码,结果看了三天也不知道整个系统有哪些功能。正确做法是先站在产品层面把功能模块拆出来,再落到代码上。这套进销存系统建议拆成下面几个模块。

用户模块:包含登录、退出、修改密码。用户分为管理员和普通操作员两类,管理员可以管理用户,普通操作员只能处理日常业务。这个模块决定了整个系统的权限基础。

基础资料模块:包含商品分类、商品信息、供应商信息、客户信息。其中商品信息是最核心的基础数据,后面所有的入库、出库操作都围绕商品展开。

采购模块:对应“进”。采购入库页面选择供应商和商品,填写数量、单价,确认后库存增加;同时记录采购流水,方便日后追溯每一批货从哪家供应商来的。

销售模块:对应“销”。销售出库页面选择客户和商品,填写数量和售价,确认后库存减少;同时记录销售流水,这是统计营业额的数据来源。

库存模块:对应“存”。提供商品当前库存查询,支持按商品名称、分类筛选;还可以做库存流水查询,看到每一件商品什么时间入库、什么时间出库、分别多少数量。

统计报表模块:用柱状图显示近七天的销售趋势,用饼图展示不同分类的商品库存占比,让数据变得直观。这个模块是答辩时最容易加分的点,因为评委能看到你用 ECharts 展示了真实业务数据。

把功能拆清楚之后,你写论文的“系统功能结构图”也顺带完成了。每个模块对应几张表、几个前端页面,心里都有数,后文讲数据库设计时你就不会觉得抽象。

2.2 数据库表设计:核心是“库存表 + 流水表”的双层结构

进销存的数据库设计是整个项目的灵魂。我的建议是总共设计六到八张表,千万不要贪多,表太多只会增加联调难度,论文画 ER 图也累。

第一张是用户表,字段包括:用户ID、用户名、密码、昵称、角色、创建时间。密码必须加密存储,不要明文保存。

第二张是商品分类表:分类ID、分类名称、备注。商品表:商品ID、商品编码、商品名称、分类ID、规格、单位、采购价格、销售价格、当前库存、库存预警值、状态、创建时间。商品编码要设计成唯一字段,这是后续查询和导入数据的依据。

第三张是供应商表:供应商ID、供应商名称、联系人、联系电话、地址、备注。客户表同理,只是字段面向销售场景。

第四张是采购入库单主表和明细表。主表记录一张入库单的整体信息:入库单号、供应商ID、入库日期、操作人、备注;明细表记录这张单里面具体进了哪些商品:明细ID、入库单ID、商品ID、数量、单价。为什么要把主表和明细表拆开?因为一张采购单可能包含多个商品,如果只放在一张表里,要么字段冗余,要么没法完整表达“一单多品”。这也是论文里必须讲清楚的数据库规范化设计。

第五张是销售出库单主表和明细表,结构跟采购单类似,只是把供应商换成客户,数量、单价换成销售场景下的数据。

第六张是库存流水表。字段包括:流水ID、商品ID、变动类型(入库/出库/盘盈/盘亏)、变动数量、变动前库存、变动后库存、关联单号、操作人、创建时间。这张表是审计和追溯的核心,也是库存统计报表的数据来源。

这里特别解释一个关键设计思想:为什么要有“库存表 + 流水表”的双层结构。库存表只记录商品的当前库存总量,它解决的是“现在还剩多少”;流水表记录每一次库存变动,它解决的是“这些数量是怎么变来的”。如果没有流水表,万一数据出问题,根本没法排查是哪一笔入库错了;有了流水表,每一笔操作都有据可查。这个设计在答辩时被问到“如果库存数据不对怎么办”,你就能理直气壮回答:通过流水表可以逐笔回溯。

在字段类型上,金额统一用 DECIMAL(10,2),数量用 INT 或者 DECIMAL(10,3) 都可以,但建议统一用 DECIMAL,因为某些商品的入库单位可能出现小数。单价、金额字段不要用 FLOAT 或 DOUBLE,那会在计算营业额时出现精度丢失,答辩现场算账对不上非常尴尬。

数据库交付的格式一般是两个文件:建表语句文件和数据初始化文件。初始化数据至少要包含一个管理员账号和十几条商品、供应商、客户数据,保证系统跑起来页面不是空荡荡的,演示效果立刻就有了。

3. 后端从 0 到 1:SpringBoot3 项目搭建全流程

3.1 环境准备与版本对照:选对版本能避开一半坑

动手之前先把环境装好。这里我把版本对照直接列出来,照着装就行。

软件推荐版本说明
JDK17 或 21SpringBoot3 必需,不要装 JDK8
Maven3.8 以上依赖管理和项目构建
IDEA2023 以上社区版也够用
MySQL8.08.0 以上,连接方式与老版本有差异
Node.js18 或 20Vite6 和 Vue3 在该版本下最稳
前端包管理器npmNode 自带

环境变量要配好 JAVA_HOME 和 MAVEN_HOME,命令行里执行 java -version、mvn -version 能看到对应版本就算通过。这里有个我见过很多次的问题:电脑上原来装了 JDK8,IDEA 里面选了 JDK17,但命令行还是 JDK8,导致项目启动直接报错。建议配置完成后,重新打开终端或 IDE,确认所有窗口都指向同一个 JDK17。

3.2 创建 SpringBoot3 后端项目

我一般不用 IDEA 自带的 Spring Initializr 向导,直接访问 Spring Initializr 网页生成更直观。需要注意的依赖有:Spring Web(提供 REST 接口能力)、Validation(参数校验)、MyBatis-Plus(数据库 ORM)、MySQL Driver(数据库连接)、Lombok(简化实体类代码)。

版本选 SpringBoot3.x 最新稳定版,Java 版本选 17,Group 和 Artifact 用自己的项目名,比如项目名可以用 inventory。生成后解压,用 IDEA 打开,等待 Maven 下载依赖。

项目包结构建议这样规划:

com.example.inventory ├── controller // 对外提供 REST 接口 ├── service // 业务逻辑层 ├── mapper // MyBatis-Plus 数据访问层 ├── entity // 数据库实体类 ├── dto // 前端传参对象 ├── vo // 返回给前端的视图对象 ├── config // 配置类,比如 CORS、拦截器 ├── common // 通用返回结果、异常处理 └── utils // JWT、MD5 等工具类

对应的核心配置文件 application.yml 写法如下:

server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/inventory?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 jwt: secret: your-strong-secret-key expire-hours: 24

配置里有两个细节值得新手注意。第一,MySQL 8 的连接地址一定要带 serverTimezone=Asia/Shanghai,否则数据库连接会报时区错误。第二,MyBatis-Plus 的逻辑删除配置建议一开始就加上,这样删除商品记录时不会物理删除数据,以后统计报表数据还能追溯回来。

3.3 JWT 登录认证:第一个核心功能

登录是系统的入口,也是权限控制的基础。我用的方案是 JWT 令牌,主要流程是:用户提交用户名密码,后端校验成功后生成一个带过期时间的加密令牌,前端保存令牌,之后每次请求都带上,后端通过拦截器解析令牌来识别用户身份。

先创建通用返回结果类,让所有接口返回格式统一:

@Data public class Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMessage("操作成功"); result.setData(data); return result; } public static <T> Result<T> error(String message) { Result<T> result = new Result<>(); result.setCode(500); result.setMessage(message); return result; } }

登录接口的核心逻辑如下:

@RestController @RequestMapping("/api/auth") public class AuthController { @PostMapping("/login") public Result<LoginResponse> login(@RequestBody @Valid LoginRequest request) { // 1. 根据用户名查询用户 User user = userService.lambdaQuery() .eq(User::getUsername, request.getUsername()) .one(); // 2. 校验密码(密码是 MD5 或 BCrypt 加密存储) if (user == null || !user.getPassword().equals(DigestUtils.md5Hex(request.getPassword()))) { return Result.error("用户名或密码错误"); } // 3. 生成 JWT 令牌 String token = JwtUtil.createToken(user.getId(), user.getUsername(), user.getRole()); return Result.success(new LoginResponse(token, user)); } }

密码加密这里多说一句:不要用明文,至少用 MD5,更推荐 BCrypt。如果用 MD5,记得在前端和后端做两段处理,前端也可以先不做,完全由后端加密。答辩时老师问到密码安全问题,你能回答出加密存储,就已经超出大多数学生了。

4. 核心业务实现:入库、出库和库存到底是怎么动态变化的

4.1 采购入库:一个事务搞定库存和流水

整个系统最核心的代码就是采购入库和销售出库。这里我详细拆解采购入库的实现逻辑。

前端提交的数据是一条采购单信息,里面包含供应商ID、入库日期、商品明细列表,每个明细里面是商品ID、数量、单价。后端要做的事情可以拆成四步:

  • 生成一个唯一的入库单号,比如用当前时间戳加随机数;
  • 往采购入库主表插入一条记录,拿到主表ID;
  • 遍历商品明细,往入库明细表插入多条记录;
  • 更新对应商品的当前库存,往库存流水表插入一条入库流水。

这四步必须保证要么全成功,要么全失败,否则就会出现“入库单有了,但库存没增加”这种数据不一致情况。所以方法上必须加事务注解。示例代码如下:

@Transactional(rollbackFor = Exception.class) public Long createPurchaseOrder(PurchaseOrderDTO dto) { PurchaseOrder order = new PurchaseOrder(); order.setOrderNo(generateOrderNo("PO")); order.setSupplierId(dto.getSupplierId()); order.setOrderDate(LocalDateTime.now()); order.setOperator(getCurrentUserId()); purchaseOrderMapper.insert(order); for (PurchaseOrderItemDTO item : dto.getItems()) { // 1. 插入入库明细 PurchaseOrderItem orderItem = new PurchaseOrderItem(); orderItem.setOrderId(order.getId()); orderItem.setGoodsId(item.getGoodsId()); orderItem.setQuantity(item.getQuantity()); orderItem.setPrice(item.getPrice()); purchaseOrderItemMapper.insert(orderItem); // 2. 更新商品库存 Goods goods = goodsMapper.selectById(item.getGoodsId()); BigDecimal oldStock = goods.getStock(); BigDecimal newStock = oldStock.add(item.getQuantity()); goods.setStock(newStock); goodsMapper.updateById(goods); // 3. 写库存流水 StockRecord record = new StockRecord(); record.setGoodsId(item.getGoodsId()); record.setChangeType("IN"); record.setChangeQuantity(item.getQuantity()); record.setStockBefore(oldStock); record.setStockAfter(newStock); record.setRelatedOrderNo(order.getOrderNo()); stockRecordMapper.insert(record); } return order.getId(); }

为什么要加 @Transactional?因为 MyBatis-Plus 默认的 updateById 是单条 SQL,本身只有原子性,但没有隔离性保证。如果第二步第三步之间程序异常退出,没有事务的话,库存改了但流水没写,后续就没法追溯。事务开启后,一旦中间抛异常,前面所有插入和更新都会回滚,数据库回到操作前状态。

4.2 销售出库与库存校验:防止库存变成负数

销售出库的逻辑逻辑上和入库对称,但多了两个关键判断:商品是否存在、库存是否充足。

新手最容易写出的错误版本是:

goods.setStock(goods.getStock().subtract(sellQuantity)); goodsMapper.updateById(goods);

这段代码在并发环境下会有问题。比如同一件商品只剩 3 件,两个用户同时下单各买 3 件,很可能两个请求都读到了库存 3,都执行扣减,最后库存变成负数。学术一点叫超卖,实际点说就是库存数据直接烂了。

处理思路有两种。第一种是悲观方案,对商品的库存行加数据库行锁,比如使用 for update 查询;第二种是乐观方案,用条件更新,强制要求当前库存大于等于扣减数量才会更新成功:

int rows = goodsMapper.deductStock(goodsId, quantity); if (rows == 0) { throw new RuntimeException("商品库存不足"); }

对应的 SQL 是:

UPDATE goods SET stock = stock - #{quantity} WHERE id = #{goodsId} AND stock >= #{quantity}

这样只有在库存充足时更新才会影响一行,否则 update 影响行数为 0,直接抛出“商品库存不足”。这个方案简单高效,也容易被答辩评委接受,属于非常典型的并发安全处理写法。

4.3 统计报表:SQL 聚合 + ECharts 展示

统计报表模块通常有两个常见图表:近七天销售趋势柱状图、商品分类库存占比饼图。

销售趋势的数据来源是销售明细表和商品表。核心 SQL 可以这样写:

SELECT DATE_FORMAT(s.create_time, '%Y-%m-%d') AS day, SUM(s.amount) AS total_amount FROM sale_order_item s WHERE s.create_time >= DATE_SUB(CURDATE(), INTERVAL 7 DAY) GROUP BY DATE_FORMAT(s.create_time, '%Y-%m-%d') ORDER BY day;

库存占比的数据来源是商品表和分类表:

SELECT c.name AS category_name, SUM(g.stock) AS total_stock FROM goods g LEFT JOIN category c ON g.category_id = c.id GROUP BY c.name;

后端把这两类聚合结果封装成 Map 或实体对象返回,前端用 ECharts 的柱状图和饼图直接渲染。这里有一个经验:如果某个日期没有销售额,GROUP BY 的结果里就不会出现当天数据,前端图表上就会少一个柱子。解决办法是在前端补齐七天日期,缺失的日期补 0,视觉上更完整。这种细节在答辩演示时非常加分,因为老师一眼就能看出你的数据处理做了充分测试。

5. Vue3 前端实战:页面从搭建到对接

5.1 使用 Vite 初始化前端项目

前端的开发环境检查好 Node 版本后,在终端执行:

npm create vite@latest inventory-web -- --template vue cd inventory-web npm install npm install element-plus axios pinia vue-router echarts

然后打开项目,目录结构建议调整为:

src ├── api // 封装所有后端接口请求 ├── assets // 静态资源 ├── components // 公共组件 ├── router // 路由配置 ├── store // Pinia 状态管理 ├── views // 页面组件 ├── App.vue └── main.js

main.js 里统一注册 Element Plus:

import { createApp } from 'vue' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' import App from './App.vue' import router from './router' import { createPinia } from 'pinia' const app = createApp(App) app.use(ElementPlus) app.use(router) app.use(createPinia()) app.mount('#app')

为什么把目录规划得这么清楚?因为后面写每个模块页面时,直接在 views 下新建对应文件夹,在 api 下新建对应 js 文件,代码定位非常容易,不会出现整个项目页面文件全堆在一个目录下的乱象。论文中的“系统实现”章节也可以按这个目录结构分页描述。

5.2 登录流程与路由守卫:前端如何保持登录状态

登录页的流程是:收集用户名密码,调用 /api/auth/login,成功后把 token 保存到 localStorage,再跳转到首页。为了避免每个请求都手动加 token,我在 axios 封装里统一处理:

import axios from 'axios' import { ElMessage } from 'element-plus' import router from '../router' const request = axios.create({ baseURL: '/api', timeout: 10000 }) // 请求拦截器:携带 token request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = token } return config }) // 响应拦截器:统一处理错误 request.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { ElMessage.error(res.message) return Promise.reject(new Error(res.message)) } return res }, error => { if (error.response && error.response.status === 401) { ElMessage.error('登录状态已过期,请重新登录') localStorage.removeItem('token') router.push('/login') } else { ElMessage.error('网络异常,请稍后重试') } return Promise.reject(error) } ) export default request

路由守卫的写法:

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

这套组合的意义是:就算用户手动删除了浏览器的登录状态,他也进不去需要权限的页面;一旦 token 过期,后端返回 401,前端自动踢回登录页。这正是评委经常问的“安全性怎么保证”的一个完整回答链条。

5.3 标准 CRUD 页面写法:表格加弹窗加分页

进销存里大多数管理页面有一个通用模板:顶部搜索栏、中间表格、底部分页、右上角新增和编辑按钮。掌握一个标准写法后,商品管理、供应商管理、客户管理这些页面就能批量复制改造。

一个典型的列表页逻辑是:进入页面时请求第一页数据,通过搜索表单携带关键词重新查询,编辑时把当前行数据回显到弹窗表单里,新增时清空表单再提交。这里最常犯的错误是表单提交后忘记刷新列表,或者分页组件 current-page 没有同步。我通常会在新增、编辑成功后统一调用一次查询方法,并且在分页变化时重新拉取数据。

实践中我建议把“列表接口”统一设计成后端分页查询:

@GetMapping("/page") public Result<PageResult<GoodsVO>> page(@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize, GoodsQuery query) { Page<Goods> page = new Page<>(pageNum, pageSize); LambdaQueryWrapper<Goods> wrapper = new LambdaQueryWrapper<>(); if (StrUtil.isNotBlank(query.getName())) { wrapper.like(Goods::getName, query.getName()); } wrapper.orderByDesc(Goods::getCreateTime); goodsMapper.selectPage(page, wrapper); return Result.success(PageResult.of(page)); }

分页的好处不只是方便展示,也是防止单次查询数据量过大拖垮服务器的常规手段,在性能上体现得出来。

6. 联调、打包与部署:让项目能真实运行起来

6.1 前后端联调:代理配置和跨域解决

开发阶段最常见的问题就是跨域报错。前端跑在 5173 端口,后端跑在 8080 端口,浏览器会拒绝跨端口请求。我推荐的做法是把前端的请求地址统一写成相对路径,然后在 Vite 的 vite.config.js 里配置代理:

export default defineConfig({ plugins: [vue()], server: { port: 5173, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } })

这样前端请求 /api/login,Vite 会自动转发到 http://localhost:8080/api/login,浏览器看到的请求路径始终是同源的,完全避免跨域问题。还有一种做法是后端配置 CORS,但代理方式更推荐,因为生产部署时前端静态资源可以直接被后端服务托管,不需要再解决跨域。

6.2 打包与部署:生成可以直接演示的产物

本地验证通过后,需要把项目打包成最终可演示的产物。

前端执行:

npm run build

会在 dist 目录生成静态文件。后端执行:

mvn clean package -DskipTests

会在 target 目录生成 jar 包。部署时最简单的方案有两种。一种是把前端 dist 目录拷贝到后端项目的 src/main/resources/static 下,然后重新打包后端 jar,前端页面和后端接口就都在一个服务里了,访问 http://localhost:8080 直接打开系统;另一种是前端独立部署在 Nginx,后端独立部署 jar 包。毕设演示用第一种就够了,简单不容易出问题。

启动命令:

java -jar inventory-0.0.1-SNAPSHOT.jar

启动前确认 MySQL 里已经执行过数据库初始化脚本,并且 application.yml 里的用户名密码跟本地一致。我建议把数据库初始化脚本单独放在源码根目录的 sql 文件夹里,方便别人复现。

7. 常见问题速查:这些坑我帮你踩过了

我把这一路下来遇到的高频问题整理成一张速查表,你遇到对应现象直接对照处理。

现象可能原因解决办法
后端启动报 “Java 版本错误”IDEA 使用的 JDK 不是 17Project Structure 中把 SDK 设置为 JDK17
Maven 依赖下载慢或失败默认中央仓库访问不稳定在 settings.xml 配置阿里云镜像
前端启动报 “Vite 版本过低”Node 版本太低升级 Node 到 18 以上
数据库连接报 “Unknown database”没有导入 SQL 脚本先执行建表脚本再启动项目
登录报 401token 失效或没有携带检查 axios 封装是否设置了 Authorization 头
分页不生效,返回全部数据缺少 MyBatis-Plus 分页插件配置 MybatisPlusInterceptor 并添加 PaginationInnerInterceptor
入库数量变负数没有做库存校验按前面的条件更新 SQL 实现
日期显示成英文格式前端没有格式化时间用 dayjs 统一格式化 YYYY-MM-DD
修改密码后无法登录前端没传角色字段登录接口返回完整用户信息并缓存
图表不显示数据后端返回结构不是数组或前端拼错字段打开控制台查看接口返回逐字段比对

其中“分页不生效”最隐蔽,因为代码不报错,就是结果不对。根本原因是 MyBatis-Plus 的分页功能需要显式注册插件,我贴一下配置:

@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }

没有这段配置,selectPage 查出的是全表数据,分页完全失效。这种问题在答辩演示时一旦暴露,观感非常差,所以建议你打包前把分页功能挨个页面测一遍。

8. 零基础同学怎么把这套项目变成自己的东西

8.1 一个月学习节奏:先跑通,再改造,后阐述

零基础不要幻想两天看完所有源码。我建议按四周计划推进。

第一周:搭环境,导入数据库,把后端和前端分别跑起来,直到浏览器能正常登录、跳转、查询商品列表。这个阶段的目标是建立信心:原来一个完整系统是可以跑起来的。

第二周:带着问题去读代码。不要从头到尾读,而是按功能读。比如先读登录功能的前后端代码,从页面触发请求,到后端接口,再到数据库表,完整走一遍链路。读完登录就读商品管理,再读采购入库。当你能用自己的话讲出“我点一下新增按钮之后发生了什么”,你就真正入门了。

第三周:开始动手改代码。最小的改法是修改系统名称、Logo、主题色;中等改法是给商品加一个字段并让它在列表和表单中都生效;进阶改法是新增一个业务模块,比如“盘点管理”。

第四周:整理论文和答辩 PPT。论文里的需求分析、功能设计、数据库设计、系统实现这几章,素材全部来自前两周的截图和记录。记得提前准备一页“系统亮点”:哪些地方用了事务、哪些地方做了并发控制、哪些地方用了图表统计。

8.2 如何避免被看出是照搬源码

很多同学直接把源码改成自己的名字就交了,这种做法风险很高,因为老师的提问只要稍微深入一点就答不上来。正确的做法是理解三个核心点:JWT 登录认证的完整流程、入库出库事务处理、分页查询原理。哪怕只是用自己的话把这三块讲清楚,老师对你的认可度就会明显不一样。

再提供几个低成本高辨识度的扩展方向。第一个,给商品管理增加图片上传功能,用本地文件存储即可;第二个,增加库存预警列表,低于预警值的商品在首页高亮提醒;第三个,导出 Excel 报表,用 EasyExcel 生成采购或销售明细表。任何一项能让系统从“基础增删改查”变成“有业务深度的真实项目”,同时也能写进论文的创新点或改进方向部分。

最后我再分享一个实际演示技巧:答辩前一天把数据库服务设为开机自启,浏览器收藏好登录页,项目用打包好的 jar 启动而不是在 IDEA 里启动。这样即使现场网络不稳定、IDEA 卡顿,你也能快速展示核心功能。退一步说,就算中间出了小故障,你能冷静说出“这个问题我已复现过,原因是……”也比支支吾吾强得多。

这套项目我前前后后带过很多轮,最大的体会是:决定毕业设计顺利与否的,往往不是基础好坏,而是能不能在第一次跑通之后立刻动手改一两个地方。哪怕只改一个字段、一个按钮样式,你对这套系统的理解都会上一个台阶。照着教程做一遍,再亲手改一遍,答辩和写简历就不会再心虚了。

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

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

立即咨询