Java毕设选什么题?如果你正在搜“基于SpringBoot的药店管理系统”,大概率是在找一个难度适中、功能够多、演示效果又直观的课题。药店管理系统在毕设里属于典型的“进销存+商城”混合体:既有管理员后台的药品入库、库存管理、过期预警,又有面向用户的药品浏览、购物车、下单流程,还牵扯到用户权限、订单状态、统计报表。一套做下来,技术点覆盖得很均匀,简历上也能写几行干货。加上SpringBoot本身是当前主流框架,和它搭配的MyBatis Plus、Vue、MySQL都是实际项目里常见的组合,所以这个题目每年都有不少同学选,而且导师也容易认可。
这篇文章我就按“把项目真正做完、做懂”的思路,结合我接触过的多个类似项目经验,把这个选题从技术选型、数据库设计、核心模块实现,到部署调试、答辩避坑,完整拆一遍。不管你是零基础刚入门,还是已经写了一半卡在某个地方,都可以照着这个思路往下走。
1. 项目整体画像与核心价值
1.1 毕设选题角度分析
药店管理系统这个题目之所以适合做毕设,核心原因是它的业务边界清晰,但功能点足够丰富。普通的图书管理系统只有CRUD,做起来快,但答辩时没什么可问的,很难展示深度。而药店管理系统天然就带着几个棘手点:药品有批次、有效期、库存上限和下限;药店销售涉及处方药和非处方药的区别;商城订单一旦生成,库存扣减和订单状态要联动;管理员、店员、普通用户的权限各不相同。这些点都是导师喜欢追问的地方,也是你能拉开差距的地方。
从工作量的角度来说,这个题目两到三周可以完成主功能,再花一周打磨细节和数据填充,整体周期可控。如果你还想要“亮点”,可以在原本基础上加个简单的销售统计图表、导出Excel报表、药品过期自动提醒,这些都是低成本高收益的加分项。
1.2 系统功能边界与角色划分
在动手写代码之前,先把角色和功能边界定下来。我见过不少同学一开始就把系统设计得特别大,又是供应商管理、又是会员积分、又是多个门店,结果到后期写不完,代码里到处是半截功能。毕设项目最重要的不是大,而是“闭环”。
一个合理的药店管理系统至少应该包含三类角色:
- 管理员:药品CRUD、分类管理、库存盘点、订单管理、用户管理、数据统计。
- 店员/操作员:药品查询、库存调整、销售出库、过期药品处理。
- 普通用户(商城端):浏览药品、搜索筛选、加入购物车、下单、查看订单、确认收货。
对应到前端就是两个界面:后台管理界面和商城用户界面。这两个界面可以共用一个数据库,但登录入口和权限控制要分开。如果你的项目是纯前后端分离,那就设计两套页面路由;如果是传统的服务端渲染,可以用角色控制渲染菜单,这部分后面再说。
1.3 为什么SpringBoot是主流选择
早些年做SSH(Spring+Struts+Hibernate)项目的很多,现在基本都切到SpringBoot了。选SpringBoot不是因为“大家都在用”,而是它的开发效率确实高。内置Tomcat,不需要单独部署WAR包;自动配置把大量XML配置消灭了;配合Spring Data JPA或MyBatis Plus,CRUD代码量能少一半。对毕设来说,SpringBoot还有一个隐藏优势:资料多。你踩到的坑,几乎都能在社区里找到对应解决方案,这对独立开发的学生来说非常友好。
另外,SpringBoot的生态可以平滑引入后续扩展:如果导师要求加Redis缓存,直接引入starter即可;如果想做接口防刷、JWT无状态登录,也有成熟方案。这比用Servlet原生开发要稳妥得多。
2. 技术选型与架构拆解
2.1 后端技术栈
给这个项目配一套最稳妥的技术选型,我建议如下:
| 技术 | 推荐方案 | 说明 |
|---|---|---|
| 核心框架 | SpringBoot 2.x | 版本稳,资料多,不要一上来追SpringBoot 3.x,Java版本和部分依赖坑比较多 |
| ORM | MyBatis Plus | 单表CRUD几乎不用手写SQL,分页插件好用,适合快速出活 |
| 数据库 | MySQL 8.x | 性能足够,工具生态好 |
| 权限 | Spring Security 或 JWT+拦截器 | 如果不想写太多配置,可以用JWT+HandlerInterceptor;想给答辩加深度,就上Spring Security |
| 模板引擎 | Thymeleaf(可选) | 如果后端渲染页面,用Thymeleaf;如果前后端分离,则用Vue+Axios |
| 前端 | Vue 2/3 + Element UI/Element Plus | 管理后台首选Element,商城页面可以用Bootstrap或原生CSS |
| 工具 | Lombok、Hutool | 简化实体类和工具代码 |
我个人的习惯是:如果从前端到后端一个人写,首选“Vue + SpringBoot + MyBatis Plus”。前端和后端通过JSON交互,职责清晰,答辩时可以明确说出哪个模块是前端哪个是后端。但如果前端基础比较弱,用Thymeleaf直接渲染页面更稳,至少不会因为跨域、打包部署等问题卡住。看你自己的情况取舍。
2.2 前端与交互方案
前后端分离是现在的主流,但毕设不一定要玩得那么花哨。我这里给两种方案:
方案一:纯后端渲染。用Thymeleaf加Bootstrap。所有页面由SpringMVC的Controller返回视图。优点是开发链路短,不需要单独启动前端服务,部署就是一个Jar包。缺点是商城页面做动态交互时比较笨拙,比如购物车加减数量,要频繁刷新页面。
方案二:前后端分离。后端提供RESTful API,前端用Vue脚手开发。优点是交互流畅,界面可以做得漂亮,商城端的商品卡片、购物车弹窗、订单流程都能做得像模像样。缺点是工程复杂度上升,需要处理跨域、Token存储、路由守卫等问题,部署时也要多处理一个静态资源目录。
对于大多数同学,我更推荐方案二,哪怕麻烦一点也值得。因为现在答辩现场,导师很大程度上会关注最终的页面效果。一个像样的商城前端,比后端Service多写十万行都管用。你不需要把Vue学得多通透,会用Axios调接口、会用Element UI表格组件,就足够应付这一个项目了。
2.3 分层架构与包结构设计
包结构直接影响代码的可读性和答辩印象分。我看到过一些同学把几十个类全丢在同一个包下面,看着就头疼。建议按职责垂直分层:
com.pharmacy ├── controller # 控制层 ├── service # 业务接口 │ └── impl # 业务实现 ├── mapper # MyBatis接口 ├── entity # 实体类 ├── dto # 前端交互对象(VO、Param) ├── config # 配置类(跨域、拦截器、扫码等) ├── common # 通用返回结果、异常处理 └── utils # 工具类这个结构最大的好处是把逻辑隔离开。比如控制器只负责收参、调服务、返回统一结果;业务层处理事务和校验;Mapper层只关注SQL。导师问某个功能时,你可以在几秒钟之内定位到相关代码,这也是答辩能力的体现。
3. 数据库设计与核心模块实现
3.1 核心表结构设计
数据库设计是药店管理系统的地基,这块如果设计不好,后面写代码会处处别扭。我建议至少设计下面这些核心表:
- 用户表(user):用户ID、用户名、密码(加密)、真实姓名、手机号、角色(管理员/店员/普通用户)、状态、创建时间。
- 药品分类表(category):分类ID、分类名称、父级ID(可支持二级分类)。
- 药品信息表(drug):药品ID、药品编码(唯一)、名称、通用名、规格、生产厂家、批准文号、分类ID、剂型、单位(盒/瓶)、零售价、进货价、是否需要处方、图片URL、描述、状态(上架/下架)。
- 库存表(stock):库存ID、药品ID、批次号、生产日期、有效期至、入库数量、剩余数量、预警下限、所属仓库。
- 购物车表(cart):购物车ID、用户ID、药品ID、数量、加入时间。
- 订单表(order):订单ID、订单编号、用户ID、总金额、实付金额、收货人、联系电话、收货地址、订单状态、支付方式、创建时间、支付时间、发货时间。
- 订单项表(order_item):订单项ID、订单ID、药品ID、购买数量、单价、总价、当时快照信息。
这里强调两个容易忽略的点。第一个是药品和库存必须分离:同一个药品可以有多个批次,每个批次有独立的有效期和库存量,这样过期预警才有数据支撑。如果你直接把库存数量写在药品表里,遇到批次处理就很尴尬。第二个是订单项里需要保存快照信息,比如药品名称、价格、厂家,避免以后药品信息修改导致历史订单数据对不上。
3.2 药品管理模块实现要点
药品管理是后台最基础的功能,也是第一个要完成的核心闭环。大致包含:药品列表查询(支持名称模糊搜索、分类筛选、是否上架筛选)、新增药品、编辑药品、删除药品、查看药品详情。对于删除,我强烈建议用逻辑删除而不是物理删除。原因有两点:一是药品可能被订单引用,物理删除会把历史订单搞出空指针;二是逻辑删除只需要加一个deleted字段,MyBatis Plus默认支持,成本极低。
药品信息中,“批准文号”和“药品编码”这两列建议加唯一索引。有些同学不太理解为什么要这么较真,实际数据里一个药品可能存在多个规格、多个厂家,如果编码不唯一,后面药品录入时很容易出现混乱。做唯一约束时,要考虑提示信息友好,可以在Controller层先用Service查询是否重复,再决定是插入还是返回“编码已存在”。
过期提醒是药品系统的特色功能。实现思路很直接:写一个定时任务,每天凌晨扫描库存表里有效期还剩30天(可配置)的批次,生成提醒记录,或者直接在后台首页用列表展示。这里用Spring自带的@Scheduled注解即可,但要注意启动类需要加@EnableScheduling开关。定时任务里不要用真实时间的字符串比较,要用日期比较工具,比如同时判断“有效期 > 今天”且“有效期 < 今天+30天”。
3.3 登录与权限控制的实现思路
药店系统有管理员、店员、普通用户三类角色,权限控制是必须做的。如果你的项目比较常规,我推荐用JWT+拦截器的方式。原因是Spring Security配置相对复杂,处理不当容易各种过滤链问题,而JWT方案只要写两个组件:Token工具类和拦截器,逻辑清晰,答辩也容易讲清楚。
大概流程如下:
- 用户提交用户名密码,后端验证通过后,生成一个JWT字符串,里面包含用户ID、角色、过期时间,用一个密钥进行签名。
- 前端拿到Token后存在localStorage里,每次请求在请求头带上Authorization字段。
- 后端写一个AuthInterceptor拦截器,检查请求头里是否存在有效Token。如果有,解析出用户信息放入ThreadLocal;如果没有,返回401。
- 针对后台管理接口,再写一个AdminInterceptor或直接在拦截器里判断角色,防止普通用户调用管理员接口。
JWT的密钥不要写死在代码里,放到application.yml中。Token有效期建议设两小时,前端在响应里捕获401后跳转到登录页。这个方案自己实现一遍,你对无状态认证的理解会立刻上升一个层次。
4. 药品商城与购物流程的关键细节
4.1 商品展示与购物车实现
商城端的功能更像是电商网站的简化版:首页轮播图(可以不放,有则加分)、药品分类列表、药品卡片(图片、名称、价格、是否处方药)、搜索框、点击进入详情页。药品列表要考虑分页,后台用MyBatis Plus的分页插件,前端用Element UI的Pagination组件。搜索建议用数据库的LIKE即可,不用上Elasticsearch,毕设阶段杀鸡不用牛刀。
购物车实现有两种做法:存数据库表和纯前端LocalStorage。如果你没登录也能加购物车,那购物车逻辑就得放在前端,提交订单时再传商品列表给后端。但这对订单金额计算和库存校验不太友好。正规一点的做法是购物车表跟用户绑定,未登录就跳转登录页。毕设演示时,演示“登录后加购物车”是说得通的,而且后端校验也顺理成章。
购物车接口至少包括:加入购物车、查看购物车、修改商品数量、删除购物车项、清空购物车。实现时注意数量更新要传商品ID和新的数量,接口要做一下基础校验,数量不能小于1。有种常见问题是用购物车表的主键ID做更新,前端刷新页面后,数据对不上,就会报错。所以不要只靠主键,要同时传用户ID和药品ID作为更新条件。
4.2 订单生成与库存扣减
订单生成是整个系统中,最需要认真对待的环节。因为这里涉及多表操作:插入订单主表、插入订单项明细、扣减库存、清空购物车。任何一个步骤失败,都会造成数据不一致。所以必须加事务。推荐在Service实现方法上加@Transactional(rollbackFor = Exception.class),这样遇到任何运行时异常都能回滚。
扣减库存时,要防止超卖。常规做法是在更新库存的SQL里加条件判断:只扣减剩余数量大于等于购买数量的记录。比如:
<update id="deductStock"> UPDATE stock SET remaining_quantity = remaining_quantity - #{quantity} WHERE id = #{stockId} AND remaining_quantity >= #{quantity} </update>如果更新影响行数为0,说明库存不足,立即抛异常,让外层事务回滚。这里有个查库存和扣库存一致性的问题:不要在Service里先查剩余库存,再判断,再更新,因为并发操作下可能超卖。直接在SQL里用条件更新更安全。虽然毕设并发量不大,但这在答辩时就是加分项,你可以主动说“用了数据库行锁的思路”。
订单编号建议用时间戳加随机数生成,不要用数据库自增ID当订单号,因为订单号要对外展示且不能关联出每天的订单量。可以用类似:
String orderNo = "PO" + System.currentTimeMillis() + RandomUtil.randomNumbers(4);记得到订单的Service方法里先计算总金额,然后从数据库重新查询最新单价,不要直接信任前端传过来的单价。这一点非常重要。如果前端把单价改了,你直接存进去就是安全事故。
4.3 支付与订单状态的模拟设计
正规药店商城的支付要对接微信支付、支付宝,但毕设项目很难拿到商户号,通常用模拟支付代替。常见做法是:订单状态默认为“待支付”,用户点击“支付”后,进入一个模拟支付页面,可以选择“货到付款”或“在线支付(模拟)”,点确定后直接回调本地接口,把订单状态改成“已支付”。这种方式演示效果好,又不会涉及真实的资金接口。
订单状态的流转建议设计成:待支付 -> 已支付 -> 已发货 -> 已完成,另有“已取消”和“退货中”等状态。管理员后台可以将“已支付”订单标记为“已发货”,用户端则可以在“已完成”状态下确认收货。这个流程简单完整,也符合电商的基本逻辑。
有一点要注意:订单列表里,用户订单可能包含多条订单项,查询时需要把订单主表和订单项表分开查,然后在Service里组装成包含明细列表的VO对象。不要只查主表然后忽略明细,那样前端展示只有总金额,没有商品列表,体验很差。
5. 部署上线与调试定制经验
5.1 本地环境搭建与运行步骤
拿到一份源码后,最头疼的是不知道从哪里开始跑。这里我整理一个可复现的步骤,适用于SpringBoot + MySQL的项目:
- 安装JDK 8或11,配置JAVA_HOME环境变量。
- 安装MySQL 8.x,建一个数据库,比如pharmacy,编码选utf8mb4。
- 用Navicat或命令行执行项目自带的SQL脚本,导入表结构和初始化数据。
- 修改application.yml中数据库用户名、密码、URL地址,URL里注意带上useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai。
- 如果是前后端分离,用IDEA打开后端项目,等待Maven依赖下载完成后启动PharmacyApplication。
- 用命令行npm install安装前端依赖,npm run serve启动Vue开发服务器。
- 浏览器访问前端地址,如果能打开登录页,说明基本通了。
我见过不少同学卡在Maven依赖下载很慢这一步。可以在settings.xml文件里配置阿里云镜像,或者使用IDE内置的Maven,并选择国内镜像仓库。另外,如果你的电脑上同时装了JDK17,有些项目是JDK8编译的,启动可能报“不支持发行版本”的问题,这时要把项目的Project Structure和Maven的JDK设置都切到8。
5.2 常见报错与排查方法
这里整理几个药店管理系统最常见的报错以及排查思路:
| 报错现象 | 可能原因 | 处理方式 |
|---|---|---|
| 启动时端口占用 | 8080被其他进程占用 | 改application.yml的server.port,或杀掉占用进程(Windows用netstat -ano查到PID) |
| 数据库连接失败 | 没建库、库名不对、密码错误 | 检查application.yml,用命令行mysql -u root -p确认连通 |
| 表不存在 | SQL脚本没导入,或表名大小写问题 | 重新导入数据库,检查表名是否和实体类注解一致 |
| 前端跨域报错 | 后端没开启CORS | 写一个WebMvcConfigurer配置CorsRegistry,或者用@CrossOrigin |
| 下单报库存为负数 | 库存初始数据不足 | 检查库存表数据,确保药品存在对应批次且剩余数量充足 |
| 登录后跳404 | 路由或静态资源路径不对 | 排查前端路由,特别是刷新页面时,history模式需要配置后端转发到index.html |
如果遇到后台返回500,优先看控制台堆栈。最常见的三大类:空指针(一般是对象没查到就调方法)、SQL索引异常(表字段名对不上)、类型转换异常(比如日期格式字符串转LocalDateTime)。平时养成在Controller层就用全局异常处理器统一捕获的习惯,返回友好提示,调试起来会舒服很多。
5.3 定制扩展的一些思路
如果你不想只做一个基础版,也还有多余时间,可以考虑在这些方向上加料:
- 销售统计图表:用ECharts或Vue的图表插件,把每天的销售额、药品销售排行展示在后台首页。需要设计一张订单表聚合查询的SQL,按日期分组、按药品分组,难度不大。
- 导出Excel:用EasyExcel或POI,将药品列表和订单列表导出成Excel文件。导师很喜欢看到这个功能,实现上也不难。
- 库存预警阈值:在原基础上增加低库存邮件或站内信提醒。
- 处方药购买约束:下订单时,如果是处方药,要求用户上传处方照片,管理员在后台审核处方后才允许付款。这个功能很贴合药店场景,做完之后整个项目的业务逻辑就更有说服力。
扩展功能不要贪多,选一个最有展示价值的做深即可。比如导出一张带统计图表的销售周报,比做一堆半成品模块要强得多。
6. 毕设答辩高频问题与避坑指南
6.1 答辩时容易问到的技术点
提交通常材料之后,导师大概率会问这几个问题,提前准备好就能从容应对:
- 系统采用什么架构?为什么选择前后端分离?
- 药品库存扣减时如何保证数据一致性?
- 密码是明文存储的吗?如何加密?
- Token失效了怎么办?如何自动续期?
- 如果多个用户同时买同一种药,会不会超卖?怎么解决?
- 过期药品提醒是怎么实现的?定时任务会不会重复执行?
- 订单状态是怎么流转的?如何回退?
回答时不要只背结论,要有代码位置支撑。比如超卖问题,就打开订单Service代码,指向那行带条件判断的update语句,解释“先执行更新影响行数,如果为0则抛异常回滚,所以即使并发过来,数据库的锁机制也会让第二次更新等待,最终只有一方成功”。这一讲,导师基本就认可了。
6.2 代码质量与项目亮点打磨
很多同学从头到尾自己写,写完却不清楚自己的项目好在哪里。答辩前请默背这三个亮点:
- 采用JWT无状态认证,避免Session在集群模式下的额外开销。
- 库存扣减使用条件更新,避免并发超卖。
- 药品和库存分离,支持多批次、有效期管理,更加贴近药店行业的真实场景。
这三个点,每一个都比单纯“增删改查”高一个档次。如果你再补充一个图表统计的页面,就更有话说了。代码命名规范也很重要,实体类字段名用驼峰,数据库字段用下划线,MapperXML里的resultMap要对应得上,别让导师看到一堆乱码。注释不需要写多么长,在关键Service方法上写清楚“这里为什么会这么设计”,效果比所有方法都写一遍更打动人。
6.3 从经验角度说的几个后知后觉
我在帮人调试这个项目的过程中,遇到过的坑也不少。第一个就是密码明文存储。有些同学演示时觉得无所谓,但答辩时一问“密码安全性”,就回答不上来。建议使用Spring的BCryptPasswordEncoder,或者MD5加盐,简单且实用。第二个是图片上传。药品图片如果使用本地磁盘存储,部署到服务器后路径会变。不要写绝对路径,应该用相对路径,比如/upload/drug/xxx.jpg,然后通过配置映射静态资源目录。第三个是时间格式。如果实体里用LocalDateTime,返回给前端时默认是一串带T的字符串,不太好看,要配置统一的Jackson格式,比如yyyy-MM-dd HH:mm:ss。
最后说一点个人体会。毕设项目说到底是用四个月时间换取一次完整的工程化训练,代码数量其实不在多,而在每一个模块能不能自圆其说。药店管理系统是一个用料扎实、功能好讲的方向,如果真的踏踏实实把订单、库存、权限这三条线理清楚,答辩和未来的面试都会觉得值。如果你正卡在某个地方,不妨先放下“我要写得多复杂”的想法,把最核心的流程代码跑通,再一步步补充细节。做项目就是这样,今天能启动、能登录、能加购物车,明天就能看到它慢慢变成一个完整系统。这种一点一点看到成果的感觉,才是做毕设最珍贵的地方。