我先说明一下:从你给的标题看,这显然是一个毕业设计/课程设计方向的完整项目交付包。我的博客就要围绕这套东西的实际开发与交付来写,从架构选型、数据库设计、前后端实现、部署、报告、答辩六个维度展开,给出真正能落地的干货,而不是泛泛介绍功能菜单。
刚开始接触这类系统的时候,很多同学容易上来就陷进代码细节里,一写就是几周,最后发现要么数据库设计有问题,要么前后端对不上接口,要么部署环节一头雾水。这篇内容我就按一套完整的毕业设计交付物来拆——程序、数据库、报告、部署、答辩指导——每块都讲清楚为什么要这样做,以及实际操作中怎么避坑。
1. 内容整体设计与思路拆解
1.1 这套技术栈为什么是"标配"
Java + Vue + Spring Boot 这套组合在近几年的毕业设计里几乎成了默认选项,背后有很现实的原因。
后端用 Spring Boot 是因为它把 Spring 家族里最繁琐的配置工作都干掉了。早期用 Spring MVC 写一个能跑起来的 Web 项目,光 XML 配置就能写几百行,数据源、事务、拦截器、视图解析器全是手写配置。Spring Boot 用自动配置加约定优于配置的思路,一个启动类加几个注解,内嵌 Tomcat 就能直接跑起来,这对学生来说实在太友好了。
前端用 Vue 则是因为它的上手曲线比 React 平缓得多。Vue 的模板语法非常直观,像v-for、v-model、v-if这些指令,学过 HTML 和 JavaScript 基础的人几乎一两天就能上手写页面。对于药店管理系统这种以表格表单为主的管理后台,Vue 的组件化开发方式能很好地控制代码复杂度。
数据库这块,MySQL 没有任何悬念。免费、轻量、文档多、社区活跃,学生机器上跑毫无压力,答辩时老师也基本都用 MySQL 来问问题。这套组合还有一个隐藏优势——网上资料特别多,遇到报错一搜基本都有现成答案,对开发经验不多的同学来说,这比技术先进性更重要。
1.2 药店业务场景的独特性
很多人觉得药店管理系统就是换个名字的 CRUD(增删改查)项目,这种理解会直接拉低系统的设计质量。
药店管理系统和普通的后台管理系统相比,有几个明显的业务特点:
第一,药品有严格的批次和效期概念。同一个药品可能进了好几批货,批号不同、生产日期不同、有效期不同,销售的时候必须先卖效期近的批次。如果数据库设计里不单独维护药品批次表,后面做效期管理、批次管理全都是空中楼阁。
第二,库存变化涉及的类型多。入库、出库、销售、退货、报损、盘点,每一种操作都会影响库存数量,而且都要留痕。设计的时候需要一个统一的库存流水表来记录每次变动,这样才能回答"这个药品库存为什么少了"这类问题。
第三,销售流程关联性强。一次销售可能涉及多条药品明细,同时可能关联会员信息、结算信息,主从表结构是必须的——销售单主表加销售明细子表,通过主键关联。只有把这种关联关系想清楚,后面的功能开发才顺畅。
第四,处方药需要登记购买者信息。根据行业规范,处方药销售要有记录,包括购买者姓名、联系方式、处方信息等,这在学校答辩时也是一个很好的业务亮点。
1.3 功能模块的合理切分
药店管理系统的功能模块划分,我建议围绕一条主线:进货(供应商)→ 库存 → 销售(顾客)→ 统计,再挂上员工和系统管理。
- 系统管理:员工账号管理、角色权限、密码修改、操作日志。权限不做太复杂,基于角色的菜单级别控制就够,比如管理员能看到全部菜单,普通员工只能看到销售和库存相关页面。
- 药品管理:药品分类、药品信息维护、批次效期管理、库存查询与预警。药品信息是核心主数据,字段要全而不冗余,比如药品名称、通用名、规格、单位、生产厂家、批准文号、销售价格、库存上下限。
- 供应商与采购管理:供应商信息维护、采购订单管理、采购入库。这部分数据流是"采购单 → 入库单 → 批次库存增加"。
- 销售管理:前台收银、销售退货、销售流水查询。这是使用频率最高的模块,交互要简单快捷。
- 会员管理:会员档案、积分管理、消费记录。
- 统计报表:销售日报、销售排行、库存预警、毛利统计。
这样的切分逻辑清晰,每个模块都能对应到药品零售行业的真实操作流程,报告里也很容易写出业务背景和分析。
2. 数据库设计与核心实现要点
2.1 表结构设计的核心思路
数据库设计是这个项目的根基,表结构一旦定错,后面改起来相当痛苦。我基于常见实践,推荐核心表结构如下:
t_admin员工表:主键、登录名、密码(MD5加盐)、姓名、手机号、角色ID、状态、创建时间。t_role角色表:角色ID、角色名称、备注。t_category药品分类表:分类ID、分类名称、父分类ID、排序。t_drug药品表:药品ID、通用名、商品名、分类ID、规格、单位、生产厂家、批准文号、零售价、会员价、库存上限、库存下限、状态。t_batch药品批次表:批次ID、药品ID、批号、生产日期、有效期、采购价格、库存数量、供应商ID。t_supplier供应商表:供应商ID、名称、联系人、电话、地址、备注。t_purchase采购单表:采购单号、供应商ID、采购日期、采购总金额、操作人ID、状态。t_purchase_detail采购明细表:明细ID、采购单ID、药品ID、批号、采购数量、采购单价、生产日期、有效期。t_sale销售单表:销售单号、会员ID、销售日期、总金额、实收金额、找零、操作人ID、支付方式。t_sale_detail销售明细表:明细ID、销售单ID、药品ID、批次ID、销售数量、销售单价、小计金额。t_stock_record库存流水表:流水ID、药品ID、批次ID、变动类型(入库/出库/销售/退货/报损/盘点)、变动数量、变动前库存、变动后库存、关联单据号、操作时间。t_member会员表:会员ID、会员卡号、姓名、手机号、积分、余额、注册日期。
这套结构里有两个容易被忽视但很关键的设计。第一个是药品表和批次表分开,药品是静态主数据,批次是动态库存载体。第二个是所有库存变动都走库存流水表,这样库存数据可追溯、可对账,出问题能查。
2.2 关键字段与约束的设计意图
先说药品价格字段。我之前见过一些项目把价格字段设计成float或double,这在 Java 里涉及浮点数精度问题——算完总价出现 0.9999999 这种莫名其妙的结果。正确做法是用DECIMAL(10,2)类型存金额,Java 实体用BigDecimal接收,计算时用BigDecimal而不是直接转 double。
药品表里必须有库存上下限字段,这是库存预警功能的数据基础。预警逻辑很简单:当某药品所有批次库存数量之和低于下限时,系统在首页给出预警提示。但要注意,如果只查库存上下限,而不考虑批次效期,会出现"库存有货但近效期不能卖"的情况,库存数量不等于可售数量。
批次表里expiry_date字段建议加索引,因为效期查询和排序是高频操作。采购明细里同时记录batch_no(批号)、production_date(生产日期)、expiry_date(有效期),入库时同步生成批次记录,这是后面所有效期管理的数据源头。任何一个环节漏配了批号,后面的批次追踪就会断链条。
员工表密码存储有个经典问题。直接用 MD5 加密其实有安全风险,因为网上有大量 MD5 彩虹表。安全做法是 MD5 加随机盐再加密,Java 里可以用DigestUtils.md5Hex(password + salt)来实现,存储的是盐值:密文这种格式,校验时间再拼接一次比较。答辩时老师问到密码安全问题,这个回答会加分。
2.3 数据库设计中的避坑经验
设计数据库时最容易踩的坑是冗余字段处理不当。比如药品分类,有些人直接在药品表里存分类名称,分类一改名就要全表更新,这是典型的反范式设计。正确做法是分类单独建表,药品表只存分类ID。
还有一个常见问题是命名不规范。表名、字段名所有的命名必须用英文字母大写或小写统一风格,不要出现拼音缩写、混用大小写、使用 MySQL 保留关键字(比如用describe作字段名)。之前见过有人把字段命名为money、count这种,虽然不是保留字但容易出歧义,建议用total_amount、stock_quantity这种见名知意的命名。
日志表和主表规模差距很大,操作日志这类流水数据会越来越多。运行一段时间后查询变慢是正常现象,答辩时如果老师问性能问题,可以回答"操作日志表按月建分区"或"定期归档历史数据",这比纯写 CRUD 有深度得多。
3. 后端核心功能实现与实操过程
3.1 Spring Boot 项目搭建与分层结构
Spring Boot 项目的创建很简单,用 IDE 的 Spring Initializr 直接生成即可。Java 版本建议 1.8 或 11(取决于学校环境和你自己机器装的 JDK),Spring Boot 版本建议 2.x 稳定版,注意不要选太高的版本——有些网上的教程基于旧版本写的,版本差异会导致配置不兼容,对新手排查起来很崩溃。
后端代码的分层我建议严格遵循主流规范,这不仅是代码组织问题,答辩时老师看你代码第一眼就是看包结构:
- controller 层:接收前端请求,参数校验,调用 service,返回统一结果。
- service 层:业务逻辑核心,事务控制在这里。库存不足以要报错,销售成功要同时生成多条记录,这些都是在 service 层用
@Transactional控制的。 - mapper/dao 层:用 MyBatis 操作数据库。
- entity 层:数据库表对应的实体类。
- common 层:统一返回结果类、全局异常处理器、工具类。
- config 层:CORS 跨域配置、拦截器配置、WebMvc 配置。
当 controller 到位、service 实现逻辑完整、mapper 写好 SQL,整个后端骨架就算完成了。实际开发时,顺序建议先写 entity 和 mapper,再写 service,最后写 controller,因为依赖关系是从下往上的。
3.2 统一返回结果与全局异常处理
前后端分离的项目,接口返回格式必须统一。我会定义一个Result类,包含三个字段:code(状态码)、msg(提示消息)、data(具体数据)。所有接口都返回这个结构,前端拿到后统一判断code是否为 200,再决定下一步操作。
public class Result { private Integer code; private String msg; private Object data; public static Result success(Object data) { Result result = new Result(); result.setCode(200); result.setMsg("操作成功"); result.setData(data); return result; } public static Result error(String msg) { Result result = new Result(); result.setCode(500); result.setMsg(msg); return result; } }全局异常处理是很多同学容易忽略的功能。没有全局异常拦截器时,代码里任何 RuntimeException 都会抛给前端一个默认的 Whitelabel Error Page,又难看又难调试。用@RestControllerAdvice加@ExceptionHandler做一个统一拦截,把所有业务异常、运行时异常都转成统一的 Result 格式返回,前端拿到错误信息直接弹提示,体验好很多:
@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(BusinessException.class) public Result handleBusinessException(BusinessException e) { return Result.error(e.getMessage()); } @ExceptionHandler(Exception.class) public Result handleException(Exception e) { e.printStackTrace(); return Result.error("系统异常:" + e.getMessage()); } }自定义一个BusinessException类,在 service 里碰到业务不满足的情况就直接抛出,比如"库存不足""药品已过期""用户名已存在",前端统一处理错误提示,这个设计在答辩时非常加分。
3.3 药品入库与库存变动的完整流程
采购入库这个模块是整个系统里最有技术含量、也最值得写进报告的核心流程。它的业务逻辑是:前端提交采购单(包含供应商信息和多条药品明细)→ 后端创建采购单主表 → 创建采购明细 → 生成批次记录 → 增加库存 → 写入库存流水。这个流程任何一个环节断了都会造成数据问题。
核心代码如下(简化后):
@Service public class PurchaseServiceImpl implements PurchaseService { @Autowired private PurchaseMapper purchaseMapper; @Autowired private PurchaseDetailMapper purchaseDetailMapper; @Autowired private DrugMapper drugMapper; @Autowired private BatchMapper batchMapper; @Autowired private StockRecordMapper stockRecordMapper; @Override @Transactional(rollbackFor = Exception.class) public void addPurchase(PurchaseDTO dto) { // 1. 保存采购单主表 Purchase purchase = new Purchase(); purchase.setSupplierId(dto.getSupplierId()); purchase.setPurchaseDate(new Date()); purchase.setTotalAmount(dto.getTotalAmount()); purchase.setStatus(1); // 1 表示已入库 purchase.setOperatorId(dto.getOperatorId()); purchaseMapper.insert(purchase); // 2. 遍历明细,逐条生成批次与库存记录 for (PurchaseDetailDTO item : dto.getItems()) { // 保存采购明细 PurchaseDetail detail = new PurchaseDetail(); detail.setPurchaseId(purchase.getId()); detail.setDrugId(item.getDrugId()); detail.setBatchNo(item.getBatchNo()); detail.setQuantity(item.getQuantity()); detail.setPurchasePrice(item.getPurchasePrice()); detail.setProductionDate(item.getProductionDate()); detail.setExpiryDate(item.getExpiryDate()); purchaseDetailMapper.insert(detail); // 检查批次是否已存在,不存在则新建 Batch batch = batchMapper.findByDrugIdAndBatchNo(item.getDrugId(), item.getBatchNo()); if (batch == null) { batch = new Batch(); batch.setDrugId(item.getDrugId()); batch.setBatchNo(item.getBatchNo()); batch.setProductionDate(item.getProductionDate()); batch.setExpiryDate(item.getExpiryDate()); batch.setStock(0); batchMapper.insert(batch); } // 批次库存增加 batchMapper.increaseStock(batch.getId(), item.getQuantity()); // 写入库存流水 StockRecord record = new StockRecord(); record.setDrugId(item.getDrugId()); record.setBatchId(batch.getId()); record.setType("采购入库"); record.setChangeQuantity(item.getQuantity()); record.setRelationNo(purchase.getPurchaseNo()); record.setOperationTime(new Date()); stockRecordMapper.insert(record); } } }这段逻辑里最核心的一点就是@Transactional注解。采购入库是多张表同时操作,如果不加事务控制,万一第 3 条明细插入失败,前 2 条已经写进数据库里了——数据就处于一种"对不上账"的脏状态。加上事务后,任何一个环节抛异常,整个操作自动回滚到初始状态,保证数据一致性。
3.4 销售出库的并发与效期控制
销售模块是另一条核心链路。前端提交销售单,传购物车里的药品列表和一个批次ID列表,后端处理逻辑是:校验药品是否有效、效期是否在安全范围内、批次库存是否充足、总金额计算是否正确,然后扣减库存、生成销售单、写入流水。
效期校验这里很容易被忽略,但却是药店系统区别于普通进销存系统的关键点。销售时通过批次查有效期,如果有效期距今少于某个阈值(比如 30 天),可以设置为不允许销售,或者在界面上做醒目的"近效期"标记。实际业务里效期不佳的药品要锁定或者特价处理,这都可以作为系统功能亮点写进报告里。
并发问题值得单独提一下。两笔销售同时抢最后一件库存,数据库层面怎么处理?MySQL 的更新语句默认加行锁,所以只要用类似UPDATE t_batch SET stock = stock - #{qty} WHERE id = #{id} AND stock >= #{qty}这种带条件的更新语句,天然能避免超卖。执行后影响行数为 0 就说明库存不足,直接提示用户,这种写法比"先查再改"安全得多。
销售退货则是反向流程:校验退货数量不能超过已售数量,批次库存增加,写入退货类型的流水,同时更新销售单状态。如果会员付款了还涉及退款逻辑,可以简化成线下退款、系统打标记,但流程要能在报告里自圆其说。
3.5 角色权限控制的实现思路
药店管理系统里有管理员和普通员工两类角色,权限控制不需要上 Spring Security 这种重框架,用拦截器加自定义注解就能实现。
思路是这样的:登录成功后把用户信息和角色ID写入 Session(或者生成 Token 存入 Redis,对毕设来说 Session 就够)。定义一个拦截器,拦截所有请求,在 handler 里判断这个接口需要什么角色才能访问。具体用自定义注解@RequirePermission("admin")标注在 Controller 方法上,拦截器读取注解并和当前用户角色比对,没有权限直接返回"无权限"。
这个方案代码量少、逻辑清晰、很容易讲清楚,而且能在答辩时展示你对权限设计的理解——基于角色的访问控制(RBAC)。比直接集成 Spring Security 更能体现自身思考。
4. 前端 Vue 页面设计与交互实现
4.1 前端项目结构与页面规划
前端用 Vue 2 + Element UI 是最稳妥的组合,Vue 3 也完全可以,但 Element Plus 的资料相对少一些,对新手来说 Vue 2 的生态更友好。项目初始化用 Vue CLI 或 Vite 都行,推荐 Vite,启动速度快很多。
前端页面结构大致如下:
- 登录页:账号密码输入,校验后跳转首页。
- 系统布局:左侧菜单栏 + 顶栏 + 主内容区域。
- 药品管理页:药品列表(分页查询、搜索)、新增/编辑弹窗、删除按钮、库存预警标识。
- 采购入库页:采购单表单(选供应商)、明细表格(多行添加药品,选批次、填数量价格)。
- 销售收银页:购物车式界面,左侧药品选择列表,右侧结算栏,支持会员卡号录入。
- 库存查询页:批次维度的库存列表,支持按效期排序筛选。
- 统计报表页:用 ECharts 展示柱状图和饼图。
路由配置分两份,一份是/login,一份是主布局下的子路由,比如/system/drug、/system/purchase、/system/sale。侧边菜单用el-menu,子菜单控制在两级以内。
4.2 简单踩坑记录:前后端联调、跨域、日期格式
前后端分离开发最坑的第一关是跨域。Vue 开发服务器默认端口是 8080,Spring Boot 默认端口是 8080 或者你配置的 8081/9999,两边端口不同,浏览器会拦截跨域请求。
解决办法有两种。第一种在前端配置代理:在vue.config.js里配置devServer.proxy,把/api开头的请求转发到后端地址,这样浏览器看到的是同源请求,生产环境再用 Nginx 反向代理。第二种在后端配置 CORS:Spring Boot 里写一个WebMvcConfigurer实现类,允许特定来源跨域。对毕设来说,前端代理方案更标准,也更接近真实项目做法,我推荐用这个。
日期格式是另一个很经典的坑。Spring Boot 默认返回的日期格式是yyyy-MM-dd'T'HH:mm:ss.SSSZ这种 ISO 格式,而前端 Element UI 的日期组件默认传yyyy-MM-dd格式。所以要么后端配置统一日期的序列化格式:在application.yml里加:
spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8要么前端传值前先格式化。两种方式都要做,前端负责传得对,后端负责返得对,两边都调整一下才能不出幺蛾子。
4.3 Element UI 的高频组件用法与性能提示
Element UI 里高频使用的组件就是表格el-table、表单el-form、弹窗el-dialog、提示el-message、下拉el-select、日期el-date-picker。
药品列表页是典型的表格页,分页建议把查询参数放在data里统一管理,方法约定为loadData(),每次点击搜索、翻页、删除后重新拉取列表。后端接口对应PageHelper.startPage(page, size)加 MyBatis 分页插件,返回的数据结构里带上total总数,前端分页组件才能正常显示总页数。
el-table的每一列建议都设定好width或min-width,不做全局设定会导致不同浏览器下布局错位。时间列设置formatter函数进行格式转换。金额列设置align="right"和sortable排序属性。这类小细节能让页面整体观感专业很多,答辩演示时老师看了也会觉得完成度不错。
还有一个性能细节:不要直接在表格里对每一行做复杂的v-if嵌套计算。比如药品名称加库存预警标识,推荐在列表查询接口的 SQL 里直接把预警状态算好,前端拿到值直接渲染,不要让前端遍历每行去判断。数据量小的时候没感觉,几万条药品时就能感到明显的卡顿差别。
4.4 前端交互流程的体验优化
药店系统的日常使用场景是店员在收银台前快速操作,所以销售页面的交互流畅度直接影响使用体验。我之前做的时候在销售收银页做了几个优化,实测效果很明显:
- 药品搜索区放大,支持拼音码搜索(比如输入"apc"能搜出"阿莫西林胶囊"),这里用 Vant 或 Element 的 autocomplete 组件,也可以直接在数据库加一个拼音码字段作为冗余条件。
- 加入购物车后默认数量为 1,双击数量可快速修改。
- 计算合计和找零放在结算栏,实时刷新。折扣金额和会员价做了联动。
- 键盘回车事件绑定到扫码枪输入框,扫完条码自动添加药品到购物车,焦点自动回到扫描框。这在真实的药店收银场景里是刚需功能,展示出来会非常加印象分。
前端不是"能跳转页面就行"。答辩演示时,老师看到扫码枪输入这种细节,会认为你真的思考过业务场景,而不是只会复制粘贴 demo。
5. 部署流程与项目文档
5.1 本地部署步骤
部署是整个项目里环节最多、最容易出问题的地方。建议部署环境用 Windows + 手动部署,先跑通再考虑 Docker 和服务器。
具体步骤如下:
- 安装 JDK 1.8,配置
JAVA_HOME环境变量。装完后在命令行输入java -version确认安装成功。 - 安装 MySQL 5.7 或 8.0,设置 root 密码为简单好记的(比如
123456,答辩机器上方便演示)。 - 用 Navicat 或命令行执行项目附带的
pharmacy.sql文件,一键导入数据库和样本数据。 - 修改后端项目的
application.yml里的数据库账号密码,改成你自己机器的实际配置。 - 启动后端:在 IDEA 里直接运行启动类,或打成 jar 包后用
java -jar pharmacy-server.jar启动。 - 前端项目在
package.json所在目录执行npm install安装依赖,再npm run dev或npm run build启动。
前端如果npm install时报错,检查一下 Node.js 版本,Vue 2 的项目一般需要 Node 14 或 16。装完依赖后npm run dev启动开发服务器,浏览器打开 http://localhost:8080 就能看到登录页。
5.2 Nginx 部署与跨域问题的生产级解法
如果要在服务器上部署(有的学校答辩要求提供演示链接),建议用前端打包成静态文件 + Nginx 托管 + 后端 jar 包运行的方式。
前端执行npm run build,打包后生成一个dist目录。把dist目录里的内容上传到服务器某个目录,比如/usr/share/nginx/html。Nginx 配置里设置root指向这个目录,并配置location /api/反向代理到 Spring Boot 服务:
server { listen 80; server_name your_server_ip; root /usr/share/nginx/html; 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; } }这样前端页面和后端接口走同一个域名和端口,不存在跨域问题。后端 jar 包用nohup java -jar pharmacy-server.jar > log.txt 2>&1 &在后台运行,保证服务器重启后程序还在。数据库装在服务器上,注意开放防火墙对应端口。这套部署方案是生产环境常见形态,把这套流程写进部署教程里,含金量比单纯"本机运行"高得多。
5.3 项目报告写作的实用框架
毕业设计报告不是期末论文,它的核心逻辑是"你做了什么、为什么这样做、效果如何"。我推荐这样一个章节框架,也基本是各高校通用的结构:
- 绪论:课题背景与意义、国内外研究现状、研究内容与方法(这部分别写太长,重点是引出你为什么做药店管理系统)。
- 相关技术介绍:Java、Spring Boot、Vue、MySQL 的关键特性和选型理由。每个技术控制在 300-500 字,别变成抄百度百科。
- 需求分析:系统角色、功能性需求(模块清单加用例描述)、非功能性需求(性能、安全、可维护性)。
- 系统设计:总体架构、功能模块设计、数据库设计(E-R 图 + 表结构)。
- 系统实现:按模块写,每个模块配界面截图和核心代码片段,代码只贴关键片段,别整页贴代码。
- 系统测试:功能测试用例表、测试结果、缺陷修复记录。
- 总结与展望:总结完成的工作,指出可改进的地方。
写报告有一个关键技巧:数据库设计部分一定要画 E-R 图,系统实现部分一定要有运行界面截图。这两样东西是老师看报告时最先翻的地方,有图比有大段文字有说服力得多。
5.4 数据库设计与前端实现如何写进报告
数据库设计部分要把每个表的关键字段用表格列出来,尤其是那些有业务意义的字段要解释清楚。比如药品分批次的逻辑、库存预警字段、累计销售数量字段。我当时写报告时,把库存流水表单独列出来详细解释——"该表记录了所有库存变动,包括采购入库、销售出库、退货、盘点,便于追溯和统计",老师在这个方向上追问了很多问题,也给了不错的评价。
前端实现部分,除了截图和代码,还需要补一个"接口联调说明"。说明某个页面调用了哪些后端接口、传了哪些参数、返回了什么数据。这样能体现你不仅会写前端,还理解前后端数据交互。如果页面里用了 Element UI 的某些特性组件,也可以简单提一下。
写报告的原则是"宁缺毋滥、重点突出"。能画出完整架构图和技术流程图,优先画架构图;代码截图只截核心几行的效果,不要截满屏。架构图推荐用 draw.io 画,免费好用,画完导出 PNG 插入 Word。
6. 答辩准备与常见问题实战
6.1 答辩前的准备清单
答辩的本质是向老师证明"系统是我做的,我懂每个环节的原理"。准备答辩前,建议按以下清单自查,能有效应付大多数提问:
- 能说清楚整个项目的技术架构和请求流转过程:登录 → 前端 API 请求 → 后端 Controller → Service → Mapper → 数据库 → 返回结果 → 前端渲染。
- 能解释项目中所有核心数据表的字段含义和表之间的关系。
- 能现场演示核心功能流程:新增药品 → 采购入库 → 查询库存 → 销售/退货 → 查看统计报表。
- 能指出系统的优点和不足,至少能说明两个改进方向。
- 能回答"为什么选择这个技术"“为什么这样设计数据库”“怎么解决某个业务问题"这类问题。
答辩时演示代码权限关系的 open 方式,建议先跑起来再展示代码,顺序是:系统界面 → 主流程演示 → 数据库设计 → 核心代码。别上来就贴代码,老师看代码的耐心很有限。
6.2 高频问题与回答思路整理
根据我见过的毕业答辩问题,整理一下高频问题和推荐回答,供参考:
为什么选择 Spring Boot?
回答思路:Spring Boot 内置了 Tomcat,无需额外部署容器;自动配置简化了项目搭建;生态成熟,与 MyBatis、Vue 等搭配方便;这些特性适合快速开发中小型管理系统。账号密码是怎么存储的?
回答思路:使用 MD5 加盐加密存储,数据库里不保存明文密码。数据库设计时预留了盐值字段,校验时用盐值结合输入密码重新计算摘要。库存预警怎么实现的?
回答思路:药品表设计了库存下限字段,批次表保存了当前各批次库存量。查询时按药品汇总批次库存,和下限对比,低于下限则预警。同时结合效期字段,近效期批次也会标记预警。一个药品对应多个批次,销售的时候怎么选择批次?
回答思路:销售时前端按药品查询所有批次,先按效期排升序(先进先出),先选择效期近的批次优先出售,确保效期管理。同时校验批次库存是否足够。数据库怎么防止超卖?
回答思路:使用带库存条件的 Update 语句,UPDATE t_batch SET stock = stock - #{qty} WHERE id = #{id} AND stock >= #{qty},更新影响行数为 0 表示库存不足,事务回滚。前端怎么和后端通信?
回答思路:通过 HTTP 请求,后端返回统一 JSON 格式。开发时用 Vue CLI 的代理解决跨域,部署时用 Nginx 反向代理。
6.3 演示翻车急救手册
答辩现场最怕代码跑不起来,或数据库连不上这类突发状况。实战经验建议如下,提前做好预案:
- 数据库连不上:先确认 MySQL 服务是否启动,Windows 按 Win+R 输入
services.msc找到 MySQL 服务手动启动。再用 Navicat 测试连接。答辩前 10 分钟提前启动好环境。 - 前端页面白屏:多半是后端启动失败导致的接口全挂。打开浏览器 F12 看 Console 报错,看是 404、500 还是跨域。500 就去后端的 IDEA 控制台看错误信息。
- 后端启动报端口占用:命令行
netstat -ano | findstr 8080找到占用进程的 PID,再taskkill /PID [进程号] /F杀掉。 - 忘记密码:直接用 Navicat 改 t_admin 表里用户记录的密码字段,生成新 MD5 密文更新进去即可。
- 演示时发现数据不对:提前准备一个重置 SQL 脚本,一键清空业务数据并重新插入干净的演示数据,答辩开始前执行一次就行。
这些平时不起眼的操作,在答辩关键时会成为救命稻草。
7. 避坑总结与个人经验心得
回头再看整个项目,从需求分析到部署答辩,最深刻的体会是——毕业设计的成败,其实在动手写代码前已经决定了。数据库设计得够不够好、技术栈选得对不对、模块划分清不清晰,这些决策层面的东西比敲代码本身重要得多。很多人一上来就打开 IDE 建项目,结果做到一半发现表结构不合理,推倒重来,非常浪费时间。
我建议动手之前花一个晚上,把数据表全部建好,把前端页面清单列清楚,把接口列表写出来。接口列表就是页面和后端之间的桥梁——每个页面需要哪些接口、传什么参数、返回什么数据,提前规划好。后面开发时按接口列表逐条实现,前后端联调就会顺畅得多。这是这几年做项目下来最实用的一条经验。
第二个经验是数据库一定要弄一份干净的初始化和示例数据脚本。做完一个功能模块就同步更新一次脚本,保证任何时候重新执行脚本都能得到一个可演示的系统状态。这份脚本不仅是你本地开发调试的保障,也是最后打包提交给老师时数据库交付物的重要内容。
第三个经验是不要做完系统才写文档和报告,而是每完成一个模块就顺手把截图、核心代码和遇到的问题记录整理下来。不然最后赶报告时,你还要重新去翻代码回忆当时怎么实现的,又费时又容易遗漏细节。答辩前老师的灵魂拷问,往往就藏在这些细节里。
这套 java+vue+SpringBoot 药店管理系统,不管是从毕设完成度、工作量、业务完整性还是答辩可展示性来说,都是很扎实的选题。照着上面的思路把每一步做扎实,项目验收和答辩都不会有问题。