每年到做毕设和课程设计的季节,我总能收到同一类问题:“基于SpringBoot的中药材店铺管理系统怎么做?源码有了但不会部署怎么办?”说实话,这种带源码、带部署文档、带讲解视频的项目包,是很多同学的第一选择,但真到自己手上,从启动到跑通,每一步都可能卡住。这篇博客我就结合这个项目,把SpringBoot中药材店铺管理系统从技术选型、数据库设计、核心业务实现到部署上线的全流程拆开讲清楚,顺便把那些“带源码也搞不定”的坑都给你填上。
这套系统本质上是一个典型的中小规模进销存管理系统,但它比普通的“学生管理系统”“图书管理系统”更有意思的地方在于:中药材这个业务领域有大量特有的规则——产地、等级、炮制方法、克斤换算、批次效期、湿度存储要求,都要落进系统里。也正因为如此,它非常适合用来练手SpringBoot + Vue前后端分离、MyBatis Plus操作MySQL、JWT鉴权、定时任务、文件上传、Docker部署这一整套现代Java Web开发的主流技能栈。
如果你正在选毕设题目,或者刚拿到一份SpringBoot项目的源码但不知道从哪下手,这篇内容可以帮你把整个项目从“能看懂”推进到“能自己跑起来、能讲明白、能应付答辩”的程度。
1. 系统整体设计与技术选型拆解
1.1 为什么是SpringBoot而不是SSH或SSM
很多同学的课程里还在教SSH(Struts2 + Spring + Hibernate)甚至更老的技术,但到了毕业设计阶段,我强烈建议你选SpringBoot。原因很直接:SpringBoot不是取代Spring,而是把Spring的配置复杂度收走了。以前SSM项目里要写一大堆XML——数据源配置、事务管理配置、MyBatis的Mapper扫描配置、包扫描配置——在SpringBoot里大部分变成了自动装配和约定优先。你只需要在pom.xml里加上依赖,再写一个application.yml,项目就能跑起来。
从答辩角度来说,SpringBoot这个选型本身就意味着你了解行业现状,因为现在绝大多数Java后端岗位面试讲的就是SpringBoot、Spring Cloud、微服务这套生态,而不是SSH。用人单位看你的简历上写着“基于SSH做的XX管理系系统”,第一反应通常是停留在五年前的水平。面试官也更愿意问SpringBoot自动装配原理、Starter机制这类有深度的问题。
另外,SpringBoot对中小型系统非常友好。像中药材店铺管理系统,用户量不大,并发不高,单体架构完全够用,不需要上微服务那套。用SpringBoot做单体应用,结构清晰、部署简单,一个jar包扔到服务器上就能跑。你与其为了简历好看硬上一个微服务组件,不如先把单体项目做扎实。
1.2 技术栈搭配与版本选型的逻辑
这个项目的技术栈是经典的“SpringBoot + MyBatis Plus + MySQL + Vue”,我用了很长一段时间,非常稳。具体版本选择上,这是第一个要命的地方,因为SpringBoot版本差异非常大。
如果你用的是JDK 8,SpringBoot就老老实实选2.7.x系列,不要上3.x。SpringBoot 3.0以后强制要求JDK 17,默认使用Jakarta EE命名空间——最直观的变化是javax.servlet变成了jakarta.servlet,MyBatis Plus也有专门的对应版本。很多同学拿到源码后直接把SpringBoot升级到最新版,结果一堆注解报错、依赖冲突,其实不是代码问题,是版本不匹配。
我当时用的是SpringBoot 2.7.18,这是2.x系列的最后一个版本,也是JDK 8下最稳妥的选择。MyBatis Plus用的3.5.x,对应MyBatis 3.5以上版本,分页插件、代码生成器都齐全。MySQL用的5.7,虽然8.0也兼容,但5.7对JDBC驱动的兼容性更好,部署时不容易出幺蛾子。前端如果是Vue项目,注意Node版本,Vue 2项目用Node 14/16,Vue 3项目用Node 16以上,这也是前端跑不起来的常见原因。
用表格总结一下我推荐的版本组合,方便对照:
| 组件 | 推荐版本 | 原因 |
|---|---|---|
| JDK | 1.8 | 与SpringBoot 2.x完全兼容,部署环境普及度最高 |
| SpringBoot | 2.7.18 | 2.x最后一个版本,稳定且资料多 |
| MyBatis Plus | 3.5.x | 内置分页插件、条件构造器,开发效率高 |
| MySQL | 5.7 | 稳定,JDBC兼容性好 |
| Node.js | 14或16 | 对应Vue 2项目构建要求 |
| Maven | 3.6.x | 兼容JDK 8,配置简单 |
1.3 项目目录结构与分层设计
拿到源码后,第一步不是急着启动,而是先看懂目录结构。这个项目采用的是标准的Maven多模块思想下的单模块分层:Controller层负责接收请求、Service层负责业务逻辑、Mapper层负责数据库操作、entity/domain层放实体类。只不过在SpringBoot项目中,这些目录都在同一个module下面,按包名区分。
看源码的时候我建议你按这个顺序去读:先看实体类(entity包),搞清楚数据库里有哪些表;再看Controller层,了解系统对外提供了哪些接口;然后看Service层,理解核心业务是怎么流转的;最后看Mapper层,重点看那些复杂的SQL是怎么写的。这个顺序能让你在最短时间内建立起对系统的整体认知。
这种分层设计的好处非常明显——职责单一、易于测试。如果答辩时老师问“为什么要把Controller和Service分开”,标准回答是:Controller层只负责参数接收和结果返回,不包含业务逻辑;Service层承载具体业务规则,可以独立进行单元测试;Mapper层屏蔽SQL细节,便于后续优化或替换持久层方案。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心业务模块与数据库设计思路
2.1 中药材特有属性建模,比普通商品表复杂在哪
中药材店铺管理系统不是简单的商品增删改查,关键在于“药材”这个业务对象有很多普通商品没有的属性。产地、等级(一级/二级/统货)、炮制方法(生用/炒制/蜜炙)、采收年份、储存条件——这些信息不仅要在商品表里体现,还会影响定价和库存策略。
在设计数据库时,主表是商品表(或者叫药材表),但绝不是只有名称、价格、库存这三个字段。我建议至少包含以下字段:药材名称、拼音码(方便快速检索,因为收银员很多时候记不住全名)、别名、产地、规格等级、炮制方法、单位(是“克”还是“斤”还是“袋”)、零售价、进货价、库存总量、预警阈值、存放位置、图片URL。
其中最容易出问题的是“单位换算”。中药材行业有个特殊习惯,进货可能是按“公斤”进,开方销售时按“克”出,有些贵细药材甚至按“克”进、“克”出。如果系统里只用单一单位,就会出现库存数字对不上的情况。设计时我建议单独建一个单位换算表,或者至少在主表里同时保存基本单位和换算比率。
另一个容易忽略的是“批次与效期”。中药材不是永久存放的,特别是一些果实种子类药材,超过保质期就要下架。因此库存表建议设计成批次维度——每次采购入库生成一个批次号,记录生产日期和有效期,销售出库时按“先进先出”规则自动选择批次。这样既能追溯来源,也能做效期预警。
2.2 采购、入库、销售、库存扣减的核心流程
这套系统的业务主链路是:采购单创建 → 审核 → 入库 → 库存增加 → 销售单创建 → 扣减库存 → 统计利润。每一步都有对应的数据表和页面。
采购模块的页面一般包含采购单列表和采购单详情。创建采购单时选择供应商、药材、数量、单价,保存后生成待审核状态的采购单。审核通过后,点击“入库”按钮,系统自动做两件事:一是把采购单状态改为“已入库”,二是在批次库存表中插入一条新批次记录,并增加对应药材的总库存量。
销售模块的逻辑是系统的核心难点。用户在收银台选择药材、填写数量,提交后后端要做的事比较多:校验库存是否充足、根据先进先出规则扣减对应批次的库存、写销售明细、更新总库存、计算销售额和毛利。这里最怕的是高并发下库存超卖——如果两个订单同时扣减同一个药材的库存,不加控制的话库存会变成负数。
我当时的做法是Service层加@Transactional事务,同时在库存表更新时使用乐观锁(UPDATE ... SET stock = stock - #{num} WHERE stock >= #{num})。这个方法不用引入Redis分布式锁,在毕设场景下完全够用,而且面试被问到时也能讲清楚为什么不用悲观锁——库存扣减场景里,大部分时间库存是充足的,乐观锁冲突概率低,性能更好。
2.3 用户权限设计:管理员、员工、收银员怎么区分
店铺管理系统的用户角色,一般有管理员、采购员、收银员(营业员)三种。管理员能看所有菜单,包括员工管理、供应商管理、数据统计;采购员只能操作采购模块和库存查询;收银员只能使用销售收银、销售记录和简单的库存查询。
权限落地的方案有两种。简单方案是用户表里加一个role字段,登录后根据角色在前端控制菜单显隐、在后端用拦截器校验接口权限。这种方案实现简单,代码量少,适合毕设。复杂方案是Spring Security + JWT做RBAC权限模型,建用户、角色、菜单、用户角色关联、角色菜单关联五张表,实现细粒度权限控制。
如果你的项目介绍里写了“基于RBAC的权限设计”,那就必须有五张表。如果只是“管理员和普通用户两种角色”,那用户表加个role字段就行,不用过度设计。我见过很多同学把权限模型做得很重,结果画蛇添足,答辩反而被问住了。明确每个模块的目标,能用简单方案解决的绝不上复杂方案,这是做项目的基本素养。
3. 关键代码实现与避坑实录
3.1 库存扣减的并发处理,为什么必须加锁
销售出库的代码是整个系统技术含量最高的地方。前端传过来的是一个销售订单的JSON数组,里面可能包含多种药材,每种药材有数量和单价,后端要在一个事务里完成全部扣减。
先看一个错误示例,这是很多初级写法:
java复制// 错误示例:先查询再判断再更新,存在并发问题
public void createSaleOrder(List<SaleItem> items) {
for (SaleItem item : items) {
Drug drug = drugMapper.selectById(item.getDrugId());
if (drug.getStock() < item.getQuantity()) {
throw new RuntimeException("库存不足");
}
drug.setStock(drug.getStock() - item.getQuantity());
drugMapper.updateById(drug);
}
}
这段代码在单线程测试下没有问题,但并发时两个请求同时查到库存是10,又同时扣减成9,最终库存就是9而不是8,这就是典型的超卖。正确写法应该利用数据库的原子更新:
java复制@Transactional(rollbackFor = Exception.class)
public void createSaleOrder(List<SaleItem> items) {
for (SaleItem item : items) {
// 原子扣减,只有库存充足时才更新成功
int rows = drugMapper.reduceStock(item.getDrugId(), item.getQuantity());
if (rows == 0) {
throw new RuntimeException(item.getDrugName() + " 库存不足或药品不存在");
}
// 写销售明细
saleItemMapper.insert(item);
}
}
对应的Mapper SQL是:
xml复制<update id="reduceStock">
UPDATE drug
SET stock = stock - #{quantity},
update_time = NOW()
WHERE id = #{drugId}
AND stock >= #{quantity}
</update>
这里update_time字段要加上,方便排查问题,这个坑我踩过。另外事务必须加rollbackFor = Exception.class,因为Spring默认只对RuntimeException回滚,如果你在业务里抛的是自定义的CheckedException,事务不会生效,库存扣了但明细没写,数据就乱了。
3.2 保质期预警怎么实现,定时任务的正确姿势
系统里“临期预警”功能非常受中药店铺欢迎。实现方案不复杂:每天定时扫描批次库存表,把剩余有效期小于30天的批次统计出来,生成预警记录,并在管理端首页展示。
用SpringBoot自带的@Scheduled注解就能实现。需要注意两点:一是要在启动类上加@EnableScheduling注解,否则定时任务不会执行;二是定时任务的执行频率不宜太高,每天凌晨跑一次即可,不需要每次请求都实时扫描。
java复制@Component
public class ExpiryWarningTask {
@Resource
private BatchStockMapper batchStockMapper;
@Scheduled(cron = "0 0 1 * * ?") // 每天凌晨1点执行
public void checkExpiry() {
LocalDate warningDate = LocalDate.now().plusDays(30);
List<BatchStock> list = batchStockMapper.selectList(
new LambdaQueryWrapper<BatchStock>()
.le(BatchStock::getExpireDate, warningDate)
.gt(BatchStock::getExpireDate, LocalDate.now())
.eq(BatchStock::getStatus, 0)
);
// 生成预警记录或通知
}
}
定时任务的cron表达式,建议先写“每1分钟执行一次”来测试,比如0 */1 * * * ?,确认数据没问题后再改成正式频率。而且要注意任务里没异常吞噬问题,如果方法内部发生异常,定时任务不会重试,也不会输出日志。排查的时候找半天发现日志里什么都没有,其实就是异常被SpringScheduled框架吞了。所以定时任务里务必加上try-catch,并输出error日志。
3.3 药材图片上传与访问路径问题
中药材系统里药材图片、药品包装图片都需要上传和展示。图片上传的实现通常是在Controller里接收MultipartFile,保存到服务器指定目录,然后把磁盘路径存到数据库字段里。
上传接口的核心代码大概是:
java复制@PostMapping("/upload")
public Result upload(MultipartFile file) {
if (file.isEmpty()) {
return Result.error("文件不能为空");
}
// 校验文件类型和大小
String originalFilename = file.getOriginalFilename();
long size = file.getSize();
if (size > 5 * 1024 * 1024) {
return Result.error("文件大小不能超过5MB");
}
String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
if (!Arrays.asList(".jpg", ".jpeg", ".png", ".gif").contains(suffix.toLowerCase())) {
return Result.error("图片格式不正确");
}
// 生成唯一文件名,防止重名
String fileName = UUID.randomUUID().toString().replace("-", "") + suffix;
File dest = new File(uploadDir + fileName);
if (!dest.getParentFile().exists()) {
dest.getParentFile().mkdirs();
}
file.transferTo(dest);
return Result.success("/images/" + fileName);
}
注意文件名一定要用UUID重命名,不能直接存用户上传的原始文件名——如果两个用户上传同名的a.jpg,后者会覆盖前者,这个坑特别经典。同时需要配置静态资源映射,把/images/**映射到上传目录。在SpringBoot里写一个WebMvcConfigurer即可:
java复制@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/images/**")
.addResourceMapping("/images/**")
.addResourceLocations("file:" + uploadDir);
}
}
在Windows本地开发时路径写D:/upload/,部署到Linux时要改成/usr/local/upload/,最好在application.yml里配置为file.upload-dir变量,换环境只改配置,不动代码。
3.4 登录鉴权:JWT的用法和拦截器配置
前后端分离项目里,登录状态管理属于必考知识点。这个系统用的JWT方案,逻辑是:用户登录时校验用户名密码,成功后根据用户ID和角色生成一个带有效期的token,前端拿到token存在localStorage里,之后每次请求在Header里带上Authorization: Bearer <token>,后端通过拦截器校验token是否合法。
JWT工具有很多,推荐用io.jsonwebtoken的jjwt库,代码非常简洁。生成token的代码大致是:
java复制public String generateToken(Long userId, String role) {
Date now = new Date();
Date expireDate = new Date(now.getTime() + 60 * 60 * 1000); // 1小时过期
return Jwts.builder()
.setSubject(String.valueOf(userId))
.claim("role", role)
.setIssuedAt(now)
.setExpiration(expireDate)
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}
拦截器里验证token时,要注意从Header中取出的值通常带“Bearer ”前缀,需要先切掉再解析。解析失败或过期时,返回401状态码,前端要统一处理,跳回登录页。
一个容易被忽略的安全问题:密钥不要硬编码在代码里,放在application.yml配置中,或者使用环境变量注入。硬编码的结果是,如果代码上传到Git仓库,等于把这个系统的所有登录凭证都公开了。还有,JWT是无状态的,服务端无法主动让某个token失效。如果要做到“用户修改密码后旧token全部作废”,简单方案是在用户表加一个token版本号或修改时间戳,后端生成token时把该值放入payload,拦截器校验时比对数据库里的值,不一致就拒绝访问。
4. 部署全流程解析:从本地到服务器
4.1 本地开发环境搭建与数据库初始化
拿到源码后第一件要做的事,不是打开IDEA直接点运行,而是先看README或部署文档。如果文档写着“导入项目后修改application.yml”,那你要做的第一步是搭建数据库环境。
我习惯用Navicat或命令行执行项目提供的sql脚本。执行之前看清楚脚本里是否包含建库语句,如果只有建表语句,需要手动先创建数据库,一般用UTF-8编码:
sql复制CREATE DATABASE IF NOT EXISTS chinese_herb DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
上方的utf8mb4和utf8有区别。早期MySQL的utf8不是真正的四字节UTF-8,遇到生僻字或Emoji符号会报“Incorrect string value”错误。数据库统一用utf8mb4,字符集校验规则用utf8mb4_general_ci,可以避免90%的中文乱码问题。
数据库导入成功后,修改application.yml里的数据源配置:
yaml复制spring:
datasource:
url: jdbc:mysql://localhost:3306/chinese_herb?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
这里最关键的参数有两个:characterEncoding=utf8和serverTimezone=Asia/Shanghai。前者保证中文不乱码,后者解决MySQL 8.x时区报错的问题。不要连useSSL=true,本地开发没有配SSL证书会频繁握手失败。
4.2 Maven打包成可执行Jar,别再装一堆环境
本地跑通后,部署到服务器有两种常见方式。第一种是直接用Maven打成可执行jar包,服务器只需要装JDK和MySQL即可。第二种是Docker容器化部署。先讲第一种,因为这是最小化部署方案。
打包前先执行Maven的clean和package,注意跳过测试:
bash复制mvn clean package -DskipTests
打包完成后,target目录下会出现两个jar包:一个是xxx.jar(可执行jar),一个是xxx.jar.original(原始jar)。如果你发现打出来的包无法用java -jar运行,先检查pom.xml里是否配置了spring-boot-maven-plugin插件,这个插件负责把依赖打进去,没有它打出来的只是个普通jar,启动时会报“没有主清单属性”。
拿到可执行jar后,上传到服务器,运行:
bash复制java -jar chinese-herb-system.jar --spring.profiles.active=prod
建议启动时指定profile,在application-prod.yml里配置生产环境数据库地址和密码,避免把本地开发配置直接带到生产环境。启动后通过日志确认端口和数据库连接情况。如果启动失败,常见的错误是端口被占用(Port 8080 was already in use)或数据库连接超时,前者换端口,后者检查安全组是否放行了3306端口。
4.3 Docker部署方式与镜像构建细节
如果你熟悉Docker,用Docker部署会省很多事,特别是以后要迁移服务器的时候。项目根目录需要写一个Dockerfile,基础镜像的选择直接决定构建速度。
如果是JDK 8项目,不要选openjdk:latest这种模糊标签,直接用openjdk:8-jdk-alpine:
dockerfile复制FROM openjdk:8-jdk-alpine
VOLUME /tmp
COPY target/chinese-herb-system.jar app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]
这里有个很容易踩的坑:openjdk:8-jdk-alpine默认时区是UTC,应用打印的日志时间会差8个小时。在Dockerfile里加一行:
dockerfile复制RUN apk add --no-cache tzdata
ENV TZ=Asia/Shanghai
至于“SpringBoot项目打包到docker desktop”这个常见问题,需要确认Docker Desktop已启动,且Windows环境下需要在项目里配置Maven的docker-maven-plugin插件,或者手动执行docker build命令。如果你发现自己跳了Docker的坑,最稳妥的路径是:本地用mvn clean package -DskipTests打出jar包,然后手动写Dockerfile执行docker build -t herb:1.0 .,再docker run -p 8080:8080 herb:1.0。这条链路可以绕开插件配置问题。
如果同时部署MySQL到Docker里,还需要配置容器网络或使用--link参数。我建议在部署文档里把“外部MySQL + Docker应用”和“全部容器化”分成两个方案,能让不同基础的同学各取所需。
5. 常见问题排查与速查表
5.1 启动阶段最常见的三个报错及排查
这个项目我帮人排查过好多遍,启动阶段的报错基本集中在三个地方。
第一个是Failed to configure a DataSource。这个报错说明SpringBoot启动时找不到数据源,原因通常是application.yml里spring.datasource配置缺失或拼写错误。注意SpringBoot默认读取application.yml或application.properties,如果你的文件名是application.yaml,或者是yml里的缩进写错了,都会导致配置加载失败。遇到这个错先看控制台提示加载了哪个配置文件,用--debug参数启动可以输出更详细的自动装配报告。
第二个是数据库连接失败报Communications link failure。本机连不上MySQL时,优先检查MySQL服务有没有启动、3306端口是否被占用、用户密码是否正确。密码里有@或#等特殊字符时,YAML文件里必须用引号引起来,否则解析出错。这是我见过的高频坑。
第三个是端口被占用。SpringBoot默认8080,如果本地装了其他服务占用8080,在application.yml里修改:
yaml复制server:
port: 8081
5.2 前端页面打不开或接口404,如何定位问题
很多时候后端启动成功,但前端页面打不开,或者页面打开了但请求接口404。这里要把情况分开看。
如果是“前端访问后端接口404”,先确认后端项目是否启动了,再确认接口路径是否写对了。用postman直接测试一下后端的接口,如果postman能通而浏览器不通,问题不在后端,而是前端代码里的请求URL写错了——很常见的是baseURL写成了http://localhost:8080/api,而后端实际接口是/api/login,就会404。
如果是“前后端分离项目,前端页面打不开”,需要区分前端是Vue项目还是打包后的静态页面。Vue项目在开发环境用npm run serve启动,默认端口是8081,如果你后端也是8080,那前端的VUE_APP_BASE_API环境变量要配置成http://localhost:8080。前端请求后端时,跨域问题通常用后端加@CrossOrigin注解或配置CorsFilter解决。最直观的排查方式是打开浏览器的F12开发者工具,看Network面板中请求的状态码和响应内容。
5.3 常见问题速查表:一步到位定位问题
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
| 启动报“主清单属性” | pom缺少spring-boot-maven-plugin | 在pom中加插件并重新打包 |
| 中文乱码 | 数据库字符集不是utf8mb4 | 修改数据库字符集,连接串加characterEncoding=utf8 |
| 接口401 | token缺失或已过期 | 重新登录获取token,检查前端是否在Header传token |
| 前端跨域 | 后端未配置跨域 | Controller加@CrossOrigin或配置CorsFilter |
| 图片上传成功但无法访问 | 静态资源映射未配置 | 继承WebMvcConfigurer配置资源映射 |
| 定时任务不执行 | 缺少@EnableScheduling | 启动类加@EnableScheduling注解 |
| 打包后运行一秒就退出 | 数据库连接失败 | 检查生产环境数据库地址、账号、密码 |
| JWT解析报SignatureException | 密钥不一致 | 确认生成和校验用的secretKey一致 |
5.4 部署到服务器后的细节问题
本地能跑通、服务器上一跑就崩,这类问题一般集中在三个细节上。
第一个是MySQL的远程访问权限默认不开放。本地连接MySQL用localhost没问题,但在服务器上部署后,如果应用和数据库同一台机器还好;如果数据库在另一台机器,就要确认MySQL用户是否允许从任意主机登录,同时安全组要放行3306端口。MySQL的root用户默认只允许localhost登录,需要手动授权:
sql复制GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'yourpassword' WITH GRANT OPTION;
FLUSH PRIVILEGES;
第二个是文件上传路径。本地Windows的D:/upload/,到Linux服务器上不存在,项目启动后上传图片会报No such file or directory。我的经验是配置目录后先用一段测试代码在启动时自动创建目录,确保目录一定存在再接收文件。
第三个是防火墙。CentOS服务器可能默认开启了firewalld,8080端口未放行,外网无法访问。在服务器上执行:
bash复制firewall-cmd --zone=public --add-port=8080/tcp --permanent
firewall-cmd --reload
如果是云服务器,还要确认安全组入方向放行8080端口。很多同学在这卡了一天,一看安全组根本没配,问题就迎刃而解。
6. 项目讲解,不只是把代码跑起来
6.1 怎么把项目讲清楚,从“能跑”到“能讲”
毕设评审时,老师最不喜欢听的就是“这个功能是网上找的代码”“这块我不太清楚”。你不需要把每一行代码都背下来,但核心模块的设计思路和关键代码逻辑一定要能画出来、讲明白。
我建议每个人都画两张图:一张是系统架构图,画清楚用户端、前端Vue、后端SpringBoot、MySQL这四层关系;另一张是核心业务流程图,画清楚销售订单从创建到扣减库存再到记录利润的完整链路。流程不用画得多复杂,关键是能自己边画边解释每一步为什么要这么做。
讲解时把系统的亮点归结为一句话:“基于SpringBoot+Vue前后端分离架构,围绕中药材领域特有的产地、炮制、批次效期等属性建模,实现了采购、入库、销售、库存预警的全链路管理,其中库存扣减采用数据库原子更新与乐观锁机制保证并发安全。”这句话覆盖了技术栈、业务领域、核心功能、技术难点四个维度,老师一听就知道你确实做了功课。
6.2 论文和文档的结构建议
配套的lw(论文/文档)结构,一般这样安排就很标准:第一章绪论讲背景和意义;第二章相关技术介绍;第三章系统需求分析,包括功能需求和非功能需求;第四章系统设计,包括架构设计、功能模块设计、数据库设计;第五章系统实现,按模块给出核心代码和截图;第六章系统测试,写测试用例和结果;最后是总结与展望。
写文档时的注意事项:不要大段大段贴代码,老师反感;不要用网上找的图片,答辩时页面都打不开,很尴尬。尽量自己截图,把主要功能页面都截下来,最好标注操作步骤。数据库设计部分给出E-R图和字段说明表,这个是最容易拿分的部分。
部署文档建议单独写一份README,内容包括环境要求、数据库导入方式、后端启动步骤、前端启动步骤、常见问题排查。这份文档的价值在于,你毕业半年后回来再看,也能按照它把系统重新跑起来。
6.3 答辩高频问题准备
答辩时最容易问到的几个问题,我提前帮你梳理一下,建议提前背熟:
第一个是“为什么选择SpringBoot”?回答要点:简化配置、自动装配、生态完善、适合快速开发中小型系统。第二个是“MyBatis Plus和MyBatis的区别”?回答要点:MyBatis Plus是MyBatis的增强工具,内置通用Mapper和条件构造器,单表操作不用写SQL,复杂查询支持自定义。第三个是“库存并发怎么处理”?回答要点:数据库原子更新 + 乐观锁,SQL里用stock >= quantity判断,更新行数为0则失败回滚。第四个是“项目有什么不足之处”?这个不要回避,你可以大方地说:目前系统是单机部署,后续可以引入Redis缓存热点药材数据和分布式锁;权限控制相对简单,后续可以引入Spring Security实现更细粒度的访问控制。这样的回答既不回避问题,又体现了你对自己项目的了解程度和下一步规划。
写在最后
这几年我看了太多SpringBoot中药材店铺管理系统相关的项目,真正能顺利跑起来并且讲明白的同学,做的都是同一件事:把源码当参考而不是当答案,每个关键模块都自己敲过、改过、调试过,遇到跑不通的地方先看日志再查资料,不硬猜。这套系统的代码并不复杂,但里面含着一个完整业务系统该有的所有要素——领域建模、事务处理、并发控制、权限管理、定时任务、文件上传、部署运维。把这些东西吃透,你学到的不仅是一个毕设项目,而是以后工作中真正会用到的一套完整方法论。
如果你现在已经拿到了源码,第一步别急着点运行,先按我上面的思路把数据库脚本执行一遍,把application.yml里的账号密码改成你自己的,再启动后端、启动前端,按这个顺序来,大部分报错都能提前避开。剩下的时间,多花在理解每一个模块的设计意图上,答辩的时候你会感谢自己当初多花了这几个小时。
