SpringBoot+Vue药店管理系统:从数据库设计到部署答辩全攻略
2026/9/9 18:44:12 网站建设 项目流程

1. 这个药店管理系统到底解决了什么问题

先说点实在的。很多Java Web方向的毕设题目,表面看是"做一个管理系统",实际上考察的是三件事:能不能把业务需求梳理清楚并转成数据库表结构,能不能用主流框架把CRUD和后端逻辑写规范,能不能让前端页面和后端接口顺利对接。药店管理系统恰好在这三件事上都有足够的发挥空间,所以它才会在SpringBoot+Vue类毕设里一直这么火。

你可能觉得"药店管理系统”不就是药品增删改查加个库存吗?真上手做会发现完全不是这么回事。药店的业务天然带有几类特殊约束,库存有批次和有效期概念,处方药销售需要登记购买人信息,药品分类和供货商、入库单、销售单之间是强关联的,盘点时还涉及报损报溢。这些约束叠加在一起,系统就不只是简单的单表CRUD了,而是一个包含多表联查、事务处理、状态流转的中小型进销存系统。这种复杂度用来做毕设刚刚好,不会难到做不完,但又能把该展示的技术点都展示出来。

从技术栈选型上说,SpringBoot负责提供RESTful接口、处理业务逻辑、对接MySQL数据库,Vue负责渲染页面、管理页面状态、调用后端接口。两者通过JSON格式的数据交互,前端不再关心SQL怎么拼接,后端也不再关心DOM怎么渲染。这种前后端分离的架构,本身就是当前企业里最常见的一种协作方式,做完这个项目,你写简历时可以很自然地把"基于前后端分离架构开发"写进去,面试官看到也不会觉得是玩具项目。

这个项目适合谁?第一类是Java Web方向的毕设学生,需要一套能跑通、能讲清楚、能通过答辩的完整项目;第二类是打算转行Java开发、想通过实战项目积累经验的求职者;第三类是已经在学SpringBoot和Vue但一直没找到合适项目练手的自学者。无论你是哪一类,带着"我要把每个模块的来龙去脉看明白"的心态去读这篇文章,收获会完全不一样。

2. 需求梳理:先弄清药店有哪些角色、哪些业务,再谈写代码

很多人做毕设最大的毛病,是一上来就建表、写代码。等写到一半发现销售单要关联库存批次,而建表时根本没考虑批次字段,又回头改表结构,改完表又要改后端代码和前端页面,整个项目就被反复推倒重来。所以先把需求梳理清楚,绝对是最省时间的环节。

2.1 从角色反推功能模块

药店的日常运营里能抽象出几类关键角色:店长或管理员,负责整体数据维护;店员或收银员,负责日常销售和客户登记;库管员,负责入库、出库、盘点。放在毕设场景里,不需要做特别复杂的权限分级,但至少要把管理员和普通操作员区分开,否则"权限管理"这个点就没法在答辩时讲。

从角色需求出发,系统功能可以拆成这么几个模块:

  • 系统管理:登录、退出、用户管理、角色管理、菜单权限
  • 药品管理:药品信息维护、药品分类、药品单位、供货商管理
  • 入库管理:入库单创建、入库明细登记、批次与有效期管理
  • 销售管理:销售单创建、销售明细、根据库存自动核减、销售记录查询
  • 库存管理:库存查询、库存预警、盘点、报损报溢
  • 统计报表:销售统计、入库统计、库存周转情况

这么拆分的好处是,答辩的时候问你"系统有哪些模块",你可以说出设计依据:每个模块都是对应角色在日常工作中的操作入口,而不是拍脑袋想的。模块之间也有清晰的依赖链,药品信息是基础数据,入库单让库存增加,销售单让库存减少,统计报表又基于这些流水数据生成。

2.2 数据流转:一张销售单背后涉及哪些表

把数据流转想清楚,建表就不会乱。以一次日常销售为例,店员选中药品加入购物车,点击结算后,前端向后端发起一个创建销售单的请求。后端接收到的数据包括:销售单主表信息(单号、操作员、总金额、付款方式)和销售明细列表(药品ID、单价、数量、小计金额)。后端要做的事不只是把这些数据插入两张表,还涉及库存判断和扣减,如果某个药品库存不足,整个销售单要能回滚,不能出现单子创建了但库存没扣的情况。

再往深层看,如果药品有批次和有效期,销售单还应该记录卖出去的是哪个批次的药品。这点很多同学容易漏掉,但现实中药店必须管理有效期,临近过期的药品要能优先出库。所以在建表时,需要有一张库存批次表,记录药品ID、批号、生产日期、有效期、入库数量和剩余数量。销售单明细要么直接关联批次ID,要么在创建销售单时通过算法从多个批次里分配数量。后者会复杂一点,但对毕设来说是很好的加分项,答辩时能讲清楚"先过期先出"的分配逻辑,评委一般都会认可。

入库到销售的完整链路是这样的:新建供货商,维护药品基础信息和分类,创建入库单选择供货商,入库单审核通过后生成批次并增加库存,销售时从有效批次中扣减数量,销售完成后生成流水,统计报表基于流水聚合。这条链路理清了,你在答辩时画数据流图也好、讲述业务流程也好,都会很有条理。

2.3 容易忽略但必须考虑的非功能需求

除了功能,还有几件事一旦忽略,后面会很痛苦。

第一是登录状态管理。登录之后前端要保存一个Token,每次请求接口时在请求头里带上,后端校验通过才返回数据。如果不用Token方案,用传统的Session也不是不行,但前后端分离项目里Session处理跨域会更麻烦,所以我更建议直接用JWT这类无状态方案。第二是操作日志。谁在什么时间做了入库操作、修改了药品价格,这些痕迹最好记录下来,虽然毕设不强制,但体现了系统设计的完整性。第三是敏感数据的脱敏和校验,比如药品ID必须存在、销售数量必须大于0、删除药品前要确认没有关联的库存和流水,这些校验在后端一定要做,不能只靠前端拦截。

把这几个非功能需求想清楚,项目就不是"能用"的水平,而是"像一个正经系统"的水平。

3. 数据库设计:12张核心表如何落地

数据库设计是整个项目的地基。表结构设计得好,后端代码写起来会非常顺,查询也不需要各种别扭的拼接。我按模块把核心表梳理了一下,并解释每一张表为什么存在、关键字段为什么这么设计。

3.1 系统管理相关表

用户表要包含id、用户名、密码、昵称、角色ID、状态、创建时间。密码一定要存加密后的密文,用BCrypt哈希,不要明文存,答辩时被问到安全问题也能答得上来。角色表和菜单表可以做成简单的两级结构,用户表通过角色ID关联角色,角色表再通过菜单权限关联可访问的菜单。如果觉得角色菜单关联太繁琐,也可以退一步做成用户表里直接存一个角色标识,但那样权限管理就不够灵活。

3.2 基础数据相关表

药品分类表字段比较基础,id、分类名称、父级ID、排序号,支持二级分类就够用。药品信息表是最核心的基础表,字段包括id、药品编码、药品名称、通用名、规格、剂型、单位、生产厂家、批准文号、分类ID、零售价、进货价、库存上限、库存下限、状态、创建时间。药品编码要设计成唯一键,入库、销售、盘点都通过药品ID关联,编码只是给人看的业务标识。

供货商表相对简单,包含名称、联系人、联系电话、地址、状态。虽然字段少,但入库单要关联它,所以也是不可缺少的基础数据。

3.3 库存和入库相关表

入库单主表记录一次入库的整体信息,包括单号、供货商ID、操作员ID、入库总金额、入库日期、状态(待审核/已入库/已作废)、备注。入库单明细表则记录这次入库里每个药品的进货价、数量、生产日期、有效期、批号。这里特别要注意,有效期和批号应该记在明细表里,因为同一张入库单里不同药品的批号不同,不能只记在主表上。

库存批次表是管理有效期的基础,记录药品ID、批号、生产日期、有效期、初始数量、剩余数量、入库单ID。库存表则可以按药品ID做汇总,记录当前总库存和可用库存。也可以不单独建库存表,直接由库存批次表按药品ID聚合查询得出总库存,但那样每次查询都要聚合,性能上不如维护一张冗余汇总表来得好。毕设阶段数据量不大,二选一都行,我更推荐同时保留批次表和库存汇总表,前者管批次,后者管预警。

3.4 销售和统计相关表

销售单主表包括单号、操作员ID、会员ID(可空)、销售总金额、实收金额、付款方式、销售日期、备注。销售单明细表包括销售单ID、药品ID、批次ID、销售单价、数量、小计金额。关联批次ID的好处是能精确知道卖的是哪个批次,也方便售后处理。如果不想做批次分配,可以暂时不关联批次,但建议至少把批次表设计出来,这样系统后续扩展不用大改。

统计报表不需要单独建表,基于销售明细和入库明细用SQL聚合就能实现。日报、月报按日期分组求和,热销药品按药品ID分组统计销售数量,这些在查询时动态生成即可。

核心表大致是:用户表、角色表(可选菜单表)、药品分类表、药品信息表、供货商表、入库单主表、入库单明细表、库存批次表、库存汇总表、销售单主表、销售单明细表、操作日志表。12张表里如果时间紧,角色菜单可以压缩,但业务主链路相关的表不建议再砍了。

4. SpringBoot 后端:接口设计逻辑与关键实现

后端是项目的中枢,业务规则、数据校验、事务控制都在这层完成。这一节我重点讲接口设计思想、核心模块的实现方式,以及几个容易踩坑的细节。

4.1 项目结构怎么分包才清晰

一个有经验的开发看到你的项目结构,基本就知道代码水平。我建议按"controller / service / mapper / entity / dto / common"分包,而不是按"controller放所有controller,service放所有service"这种极简方式。因为业务模块多的话,所有Controller堆在一个包下,后期找文件非常痛苦。

更合理的做法是,在controller和service包里再按业务模块分子包,比如:

controller ├─ system (登录、用户) ├─ drug (药品、分类、供货商) ├─ stock (入库、盘点、批次) ├─ sale (销售) └─ report (统计)

这样包名即模块名,一个类的归属一眼就能看出来。entity里放与数据库表对应的实体类,dto里放接口接收参数的包装对象。为什么要区分entity和dto?因为前端传来的参数未必和表字段一一对应,比如创建销售单时,前端传的是一个主表对象加一个明细列表,这种结构就不能直接用一个entity接收,需要单独定义一个销售单创建的DTO。DTO还能承载校验注解,比如数量不能为空、必须大于0,在校验阶段就拦截掉非法请求。

4.2 统一返回结构和异常处理

前后端分离项目里,接口返回格式一定要统一。我习惯定义一个Result对象,包含code、message、data三个字段。成功时code为200,data里放业务数据;失败时code为自定义错误码,message里写清楚错误原因。前端拿到统一结构后,只做一次统一处理即可,不需要每个接口单独判断格式。

统一异常处理用@RestControllerAdvice加@ExceptionHandler实现。这样做的好处是,Service层只需要把业务异常抛出来,不用在每个Controller里写try-catch。比如库存不足时抛一个BizException("药品XXX库存不足"),全局异常处理器捕获后返回code为500的Result,前端弹窗提示用户。这比在每个接口里手写返回失败逻辑要干净得多,也能避免漏处理异常导致前端收到一堆奇怪的报错。

4.3 销售单创建的事务控制和批次扣减

销售模块是整个系统里最容易出错的地方,而最能体现代码水平的就是创建销售单。前面说过,这个操作涉及销售单主表插入、销售单明细表批量插入、库存批次表扣减、库存汇总表更新,四个动作必须在一个事务里完成,任何一个失败都要全部回滚。

在SpringBoot里,直接在主逻辑方法上标注@Transactional就能做到事务控制。但光有注解不够,还要把库存扣减的顺序和判断逻辑写对。我建议的顺序是:先校验所有药品和数量,再循环检查每个药品的总库存是否充足,然后做批次扣减,最后插入销售单据。为什么先全部校验再扣库存?因为如果在扣了第一个药品库存之后,发现第二个药品库存不足抛出异常,虽然事务会回滚,但代码执行到一半才失败,逻辑上不够干净。先校验后扣减,能更早地让不合法请求失败。

批次扣减逻辑需要特别说明。一个药品可能对应多个批次,出库时要先扣有效期更近(即先过期)的批次。做法是查询该药品所有剩余数量大于0的批次,按有效期升序排序,然后依次扣减。第一个批次数量不够就扣完,再扣下一个批次,直到满足本次销售数量。这个过程在Java里用循环实现并不复杂,关键是思路要清晰:先把批次列表查出来,维护一个待扣数量,逐个批次处理,待扣数量减到0就结束。

4.4 查询接口里的动态SQL与多表联查

药品列表、销售记录这类查询接口,一般都需要支持按关键字筛选,比如按药品名称模糊查询、按分类过滤、按时间范围过滤。在mybatis-plus里可以用LambdaQueryWrapper拼接条件:

QueryWrapper<Drug> wrapper = new QueryWrapper<>(); if (StringUtils.isNotBlank(keyword)) { wrapper.like("drug_name", keyword).or().like("drug_code", keyword); } if (categoryId != null) { wrapper.eq("category_id", categoryId); }

如果关联表多,比如销售记录要显示操作员名称、药品名称,不能只查销售表本身。这时可以写自定义SQL,用JOIN查询把需要的字段一次性查出来,再通过分页插件返回。MyBatis-Plus的分页插件配置很简单,加一个MybatisPlusInterceptor,注册PaginationInnerInterceptor即可。注意新版和旧版的依赖坐标不一样,旧版用的是com.baomidou:mybatis-plus-boot-starter,新版分页插件类名有所调整,不要照抄老博客。

Inventory库存预警的实现也很直接:查询库存汇总表里当前数量小于库存下限的药品,前端在库存页面显示一个预警标签。这个功能看起来简单,但是很实用,答辩时也能作为业务亮点描述。

5. Vue 前端:页面怎么组织、接口怎么对接

前端部分的核心不是把页面做得花里胡哨,而是要把"页面状态"和"后端数据"之间的对应关系理清楚。我用的是Vue3加Element Plus的组合,这也是目前SpringBoot+Vue毕设里最常见的搭配。

5.1 前端项目结构和路由设计

前端项目建议用Vite创建,相比Webpack,Vite启动速度快很多,配置也简单。创建完项目后,按功能模块组织目录:

src ├─ api (接口请求封装,按模块拆文件) ├─ router (路由配置) ├─ store (全局状态,登录信息) ├─ views (页面组件,按模块分文件夹) ├─ components (公共组件) └─ utils (工具函数、axios封装)

路由设计上,建议先做一个整体布局组件,包含侧边栏菜单和顶部导航,然后在布局组件里嵌套各个业务页面。这样菜单和页面主体的关系一目了然,也方便做权限控制。菜单列表最好由后端接口动态返回,前端根据菜单数据渲染侧边栏,这样权限和菜单是一套配置,后期调整不需要改前端代码。

5.2 axios 封装与登录Token处理

axios封装是整个前端对接后端的关键。如果不封装,每个页面都写一段axios.get,重复代码多,而且出错时错误处理不统一。我习惯这么封装:

import axios from 'axios' const service = axios.create({ baseURL: '/api', timeout: 10000 }) // 请求拦截器:自动带上token service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = 'Bearer ' + token } return config }) // 响应拦截器:统一处理code和错误 service.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.data }, error => { if (error.response?.status === 401) { // token失效,跳转登录页 router.push('/login') } ElMessage.error('请求失败,请稍后重试') return Promise.reject(error) } )

请求拦截器负责把Token塞进请求头,响应拦截器统一处理错误和业务码。这样每个业务页面里调用接口时,只需要关心成功后的data数据,不用反复写错误提示。

5.3 核心页面:销售收银台的实现思路

销售收银页是前端最复杂的页面,但也最能体现你的水平。它本质上是一个动态表格:左侧是药品搜索区,输入关键字搜索药品后,选中药品加入购物车列表,购物车表格列包括药品名称、规格、单价、数量、小计;右侧是结算信息区,显示合计金额,点击结算按钮后把购物车数据提交给后端。

购物车在前端可以用reactive数组维护,每次添加药品时先判断数组中是否已有同一药品,有就把数量加1,没有就push一条新数据。数量字段用el-input-number组件,监听数量变化时自动重算小计金额。结算按钮点击后,组装出后端要求的销售单创建接口的payload结构,调用API提交。提交成功后清空购物车,刷新库存列表。

这个页面用到的Vue知识点包括响应式数据、组件事件、计算属性、双向绑定,几乎覆盖了Vue3核心内容,答辩时你可以把这页单独拿出来讲。

5.4 药品管理页的增删改查范例

药品管理页是标准的CRUD范例,包含搜索条件区、表格区、分页区和新增/编辑弹窗。搜索条件区绑定keyword和categoryId,点击搜索时重置当前页为1并调用查询接口;表格区通过el-table展示数据,操作列放编辑和删除按钮;分页用el-pagination,切换页码时触发查询。

新增和编辑可以共用一个弹窗组件,弹窗里是el-form表单,通过dialogTitle区分是新增还是编辑。打开编辑时,要把当前行数据深拷贝到表单里,避免修改弹窗内容时影响表格行数据。提交时根据是否有ID决定调用新增接口还是更新接口。删除操作要加二次确认,前端用ElMessageBox.confirm做确认弹窗,后端也要校验该药品是否已被引用。

这套CRUD写法一旦熟练,其他页面的增删改查基本都是复制改改,所以第一个完整页面尽量做扎实,后面的效率会大幅提升。

6. SQL 脚本与初始化数据的价值:从零还原一个可运行系统

一个项目能否被别人顺利跑起来,SQL脚本的质量占一半。很多同学没有这个意识,觉得SQL脚本就是把Navicat导出的SQL丢进项目里。结果别人拿到项目后,数据库建不起来、表数据对不上、账号密码登不进去,体验非常差。

6.1 脚本应该包含哪些内容

一份完整的SQL脚本至少要包含四部分:建库语句、建表语句、初始化数据、测试数据。

建库语句要指定字符集为utf8mb4,排序规则选utf8mb4_general_ci即可。utf8mb4和utf8的区别在于能存emoji字符,像药品备注里如果有生僻字或特殊符号,utf8mb4更稳妥。

建表语句里,每张表都要写清楚主键、NOT NULL约束、默认值、注释。字段注释特别重要,别人拿到脚本看字段一眼就知道用途,不需要对照代码猜。初始化数据至少要包含一个管理员账号,密码建议用代码里同一个BCrypt加密工具生成的密文,这样后端启动后可以直接用管理员账号登录。

测试数据不要贪多,但要有代表性。药品分类来个5条左右,药品信息来个20条左右,供货商来5条,入库单和销售单各来几条,库存批次和库存表的数据要和入库单对得上。重点是把有效期设计成不一致的状态,有的批次快过期了,有的批次有效期还长,这样演示库存预警和批次扣减逻辑时有真实感。

6.2 如何生成一份别人能顺利执行的脚本

很多人直接在Navicat里右键导出SQL,这种脚本里常常包含DROP TABLE语句、外键约束定义、繁琐的索引定义,别人执行时如果顺序不对很容易报错。更稳妥的做法是,你手动整理一份按逻辑顺序排列的脚本。

我先写好建库语句,然后按依赖顺序建表:先建分类、供货商这类无外键依赖的基础表,再建依赖它们的药品表,最后建入库、销售、库存相关表。如果表间有外键,建议不要在数据库层面强加外键约束,而是由业务代码保证数据的完整性。这样做有两个好处:一是删除数据时不用纠结外键冲突,二是数据库性能会好一点,毕竟毕设阶段也没有并发压力问题。

插入初始化数据时,主键ID最好显式指定,不要依赖自增。因为销售单明细、入库单明细都要引用其他表的主键ID,如果不指定,执行后你不知道系统自动分配了什么ID,数据就关联不上。

6.3 数据库脚本和后端配置的联动

脚本里建的数据库名,要和后端application.yml配置的jdbc url一致。很多人项目跑不起来,就是建了数据库叫pharmacy_db,配置里却写localhost:3306/medical,这种低级错误排查起来很费时间。所以脚本开头我通常会写清楚数据库名,并注释说明账号密码默认配置。

application.yml里还要关注时区配置。MySQL 8.x的连接URL建议加上serverTimezone=Asia/Shanghai,否则插入时间字段时可能差8小时。字符集参数characterEncoding=utf8也要带上,避免中文乱码。

7. 接口文档:让前后端协作不再靠猜

接口文档是SpringBoot+Vue项目里非常容易被忽视但实际价值很大的部分。毕设阶段你可能觉得"就我一个人开发,要什么文档",但工作后你会发现,接口文档是前后端协作的契约。而且毕设答辩时,拿出规范接口文档,评委对你的项目规范性评价会明显不一样。

7.1 为什么建议用Swagger/Knife4j

手工维护接口文档的痛点是,接口改了一处,忘记更新文档,前端拿旧文档对接,结果一堆接口报错。Swagger这类工具可以直接从代码注解生成在线文档,接口路径、请求参数、返回结构都从代码里读取,天然不会出现“代码改了文档没改”的问题。

Knife4j是Swagger的增强UI,界面比原生Swagger好看,还提供了调试功能,可以在文档页面直接调用接口。用Knife4j之后,前端同事调试接口不再需要打开Postman,直接在文档页试,效率高很多。

7.2 在SpringBoot里集成Knife4j

集成Knife4j比较简单,引入依赖,写一个配置类即可:

@Configuration @EnableKnife4j public class Knife4jConfig { @Bean public OpenAPI openAPI() { return new OpenAPI() .info(new Info() .title("药店管理系统接口文档") .version("1.0") .description("SpringBoot+Vue药店管理系统后端接口文档")); } }

为了让文档可读性好,建议在Controller类上用@Tag注解说明模块名,在方法上用@Operation注解说明接口用途,在实体字段上用@Schema注解补充说明。这些注解看起来琐碎,但写完以后文档的可读性天差地别。比如用户登录接口,@Operation(summary = "用户登录")配合参数的@Schema(description = "用户名"),前端看文档就能直接知道要传什么,不用再来问你。

7.3 接口文档里的几个关键设计决策

统一返回结构在文档里要体现清楚。如果返回结构本身就是Result ,Swagger能自动解析泛型里的data结构,前端能直接看到接口成功时data里具体有哪些字段。

接口鉴权也要在文档里配置。Swagger集成后需要设置安全方案,让文档页可以输入Token后再调接口。Knife4j里可以配置一个全局的Authorization头,这样调试需要登录的接口时,先调用登录接口拿到Token,填入全局参数后所有接口都能直接调试。

如果项目里配置了context-path,访问Knife4j文档的路径也会带上前缀,比如http://localhost:8080/api/doc.html,这个细节经常有人踩坑,记一下能帮别人省不少时间。

8. 部署演示与答辩准备:项目做完只是第一步

项目代码写完只是开始,真正让项目完整体现价值,还要经历部署、演示、答辩这几个环节。我分别说一下这个阶段要注意的事情。

8.1 前端如何打包并与后端联调

开发环境下,前端通过Vite的代理解决跨域问题:

// vite.config.js server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }

生产部署则更简单,先npm run build打包出dist目录,这个目录是纯静态文件。如果你想把前端和后端放一起启动,可以把dist里的文件复制到SpringBoot的resources/static目录下,然后后端直接访问http://localhost:8080/index.html就能打开页面,省去单独部署Nginx的步骤。这种方式对毕设演示来说方便很多,一台机器一个进程就能跑完整套系统。

接口路径要注意,如果前端请求的是/api开头,后端接口路径要与之对应。更稳妥的做法是给后端统一加一个context-path:server.servlet.context-path: /api,这样后端接口路径天然带/api前缀,前端baseURL也指向/api,打包后路径不用改。

8.2 演示数据的准备技巧

演示系统最怕临时造数据造不出来。提前在数据库里准备一套连贯演示数据,演示时按业务顺序走:先展示药品列表,再创建一张销售单,演示库存自动变化,然后查询销售记录和统计数据。每一步的数据都是前后呼应的,评委一看就知道系统是真的在跑而不是摆样子。

有个小技巧是,演示前把库存预警阈值调低一点,故意留两个药品库存接近下限,演示时刷新库存页面,预警标签亮出来,效果非常直观。如果运气好,还能碰到药品正好缺货,此时创建销售单会报"库存不足"的提示,这反而是很好的展示机会,因为业务校验确实生效了。

8.3 答辩时容易被追问的问题

评委基本不会逐行看代码,但喜欢围绕设计决策提问。我总结几个高频问题和相对稳妥的回答方向:

关于事务:创建销售单时为什么要用@Transactional?回答是涉及多张表操作,必须保证数据一致性,任何一个步骤失败都要回滚,不能让销售记录存在但库存没扣。关于批次管理:为什么批次表里有有效期?回答是药品行业合规要求,必须能追溯到每一批药品的进销存记录,并支持先过期先出的出库策略。关于权限:前端跳转为什么拦不住恶意请求?回答是前端控制只是用户体验层面的限制,真正的权限校验在后端接口层,需要结合Spring Security或拦截器实现。这个回答能体现你对前后端安全的认知层级。

8.4 项目打包发布时的注意事项

后端打包用Maven的package命令即可,打包前确认测试是否跳过,mvn package -DskipTests能省去测试执行时间。打出的jar包直接用java -jar启动,前提是本机装了对应版本的JDK。注意SpringBoot版本和JDK版本要匹配,比如SpringBoot 2.7对应JDK8或JDK11,SpringBoot 3.x必须使用JDK17以上,这个对应关系搞错,项目很可能直接启动失败。

前端打包也有坑:如果你在api配置里写了baseURL: '/api',但后续又想单独部署前端,就需要在Nginx里配置反向代理,把/api开头的请求转发到后端服务。毕设演示一般用不上Nginx,但单独部署时一定要知道这个流程:前端请求/api/login → Nginx代理转发到localhost:8080/api/login → 后端处理返回。这个链路理清了,网络配置层面的问题基本都能排查明白。

9. 做这个项目时踩过的坑和沉淀的个人经验

最后聊点书上不太会写、但实际做项目一定会遇到的细节。这些经验是我自己折腾完整个项目后沉淀下来的。

第一个坑是时间字段的时区问题。最初我把数据库时间字段设为datetime,后端用LocalDateTime接收,却没在MySQL连接URL里配置时区。结果插入销售记录后查询发现创建时间和当前时间差了8个小时。后来在jdbc url加上serverTimezone=Asia/Shanghai,问题才解决。这个坑在答辩前的演示环节出现会非常尴尬,建议一开始就配好。

第二个坑是我在创建销售单时最初没做批次扣减,只做了简单的总库存扣减。后来想在答辩里展示"先过期先出"的业务逻辑,业务里还得检查是不是快到保质期了,改成批次扣减后代码量大了一些,但整体可讲的东西明显变多了。所以这里建议,如果你时间来得及,批次扣减这个功能一定要做,它是区别于普通进销存系统的差异化亮点。

第三个坑是前端Element Plus版本和Vue版本的匹配问题。Element Plus只支持Vue3,如果误装了面向Vue2的Element UI,启动时一堆组件不生效。创建项目前先确认package.json里vue版本是3.x,再装element-plus和@element-plus/icons-vue。这个顺序反了会浪费很多时间。

关于代码规范,我的建议是接口路径命名尽量统一。自己开发时没人管你,但答辩或简历展示时,规范的RESTful风格路径会显得专业很多。比如/api/drug/list/api/drug/add/api/sale/create,语义清晰,前端对接时也不容易混淆。

关于框架版本,有一个原则值得记住:不要盲目追新。SpringBoot 2.7是一个很稳定的版本,相关资料多、踩坑方案好搜;SpringBoot 3.x虽然新,但和旧教程差异较大,毕设阶段求稳最重要。同理,Vue3相关生态已经成熟,首选Vue3加Element Plus,不要再从Vue2开始学了。

前端接口对接时还有一个容易忽略的问题:后端返回的时间字段如果格式不统一,前端表格会显示很丑的时间戳。后端在配置里统一格式化时间,用@JsonFormat注解处理LocalDateTime字段,或者配置Jackson的全局日期格式,前端显示就能保持一致。

环境搭建上,我强烈建议把本地开发环境统一:JDK版本、Maven版本、Node版本、MySQL版本都记录下来,写到项目的README文件里。这个习惯一开始看起来没什么用,但当你换电脑、或者同学拿到你的项目跑不起来时,会发现有一份环境说明是多么幸福的事。很多常见的启动失败问题,核心原因都是别人JDK版本不对或Node版本不兼容。

最后说说做完整项目的思路。这个项目虽然是毕设,但它的结构和一个真实的中小型管理系统已经非常接近了:有基础数据管理,有带事务的核心业务,有权限控制,有统计报表,有接口文档,有初始化脚本。你把它当成一个正规项目去做,每一层的代码都整理清楚,答辩和以后面试都有拿得出手的东西。事实上,很多我认识的开发,第一份实习offer靠的就是这种完整项目里积累的经验——他们能聊清楚表结构为什么这么设计,接口为什么这样拆分,事务为什么加在那里,而这些恰恰是面试官最看重的动手能力体现。

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

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

立即咨询