药房管理系统毕业设计:从零实现一个高内聚低耦合的入门级架构
1. 背景痛点:为什么“能跑就行”的代码在答辩时总被怼?
做毕业设计时,很多同学把“药房管理系统”当成“药品 CRUD 大合集”:一个DrugController里塞满增删改查,SQL 拼在 Java 字符串里,前端把表格数据一股脑儿v-for出来。结果演示时老师一句“如果药库管理员和售药师权限分开怎么做?”直接原地社死。
典型症状总结如下:
- 功能堆砌:业务代码、权限判断、数据校验全写在 Controller,方法行数飙到 300+。
- 硬编码:数据库连接、文件上传路径、角色名称直接
static final写死,换环境就崩。 - 安全裸奔:拼接 SQL、返回全字段实体、未做参数过滤,Postman 一把梭就能删库。
- 无扩展点:药品字段加个“保质期”要改 5 张表 3 层代码,牵一发动全身。
毕业设计不是“能跑就行”,而是“跑得快、改得动、讲得清”。下面给出一条最小可运行、又足够拆分的架构路线,Spring Boot + MyBatis + Vue,3 天可搭出骨架,1 周能写 PPT。
2. 技术选型:为什么不是 Django / JSP / 纯 SSR?
| 技术 | 放弃理由 | 选择理由 |
|---|---|---|
| Django | Python 语法简洁,但国内药房项目主流是 Java 技术栈,后续二开、接入医保接口时 Java 生态更香 | 无 |
| JSP + Servlet | 模板与后端高耦合,前端同学无法并行开发;IDE 支持弱,调试效率低 | 无 |
| Spring Boot | 自带 IOC、AOP、Starter,一键跑起内嵌 Tomcat,Maven 依赖管理成熟 | 选 |
| MyBatis | 比 JPA 易控 SQL,药房报表字段多且复杂,需要手写多表关联 | 选 |
| Vue3 + ElementPlus | 组件化开发,双向数据绑定,打包后纯静态文件,与后端仅通过 JSON 交互,真正做到前后端分离 | 选 |
一句话:Java 系毕业设计,Spring Boot 是“默认答案”;MyBatis 让你写 SQL 不慌;Vue 让页面不再刷新全屏白。
3. 核心模块拆解:高内聚低耦合从“分包”开始
3.1 项目结构(仅列关键目录)
text com.example.pharmacy ├─ common // 全局异常、分页、常量 ├─ config // MyBatis、Swagger、Cors、RBAC 配置 ├─ controller // 仅接收与校验参数,一行代码不写业务 ├─ service // 接口 + 实现,事务在此层开 ├─ mapper // MyBatis XML 与接口 ├─ domain // PO + DTO + VO,每层只看到自己需要的数据 └─ security // SpringSecurity + JWT,RBAC 模型3.2 药品库存管理——“只写库存,不管卖”
业务场景:药库管理员做采购入库、盘亏盘盈;售药师做销售出库。两个角色看到的数据一样,但操作按钮不同。
表设计
drug(药品字典) ← 1:N →drug_stock(批次库存,含生产日期、有效期、数量)关键接口
POST /api/stock/in入库PUT /api/stock/check盘点GET /api/stock/page库存列表(带有效期预警 ≤ 90 天)
代码片段(Java)
@RestController @RequestMapping("/api/stock") @RequiredArgsConstructor public class StockController { private final StockService stockService; @PostMapping("/in") public R<Void> stockIn(@Valid @RequestBody StockInDTO dto){ // 1. 参数校验由 @Valid + DTO 注解完成 // 2. 业务逻辑下沉到 service,Controller 仅做路由 stockService.saveStockIn(dto); return R.ok(); } } @Service public class StockServiceImpl implements StockService { private final DrugStockMapper drugStockMapper; private final DrugMapper drugMapper; @Override @Transactional(rollbackFor = Exception.class) public void saveStockIn(StockInDTO dto){ // 1. 根据药品 ID 查字典,不存在抛 BizException Drug drug = drugMapper.selectById(dto.getDrugId()); if (drug == null) throw new BizException("药品不存在"); // 2. 构造库存记录 DrugStock stock = new DrugStock(); stock.setDrugId(dto.getDrugId()); stock.setBatchNo(dto.getBatchNo()); stock.setQuantity(dto.getQuantity()); stock.setExpireDate(dto.getExpireDate()); drugStockMapper.insert(stock); } }- 前端 Vue 调用
// api/stock.js import request from '@/utils/request' export function stockIn(data) { return request({ url: '/api/stock/in', method: 'post', data }) } // views/stock/In.vue import { stockIn } from '@/api/stock' const submit = async () => { await stockIn(formModel) ElMessage.success('入库成功') }Controller、Service、Mapper 各管一摊,后期想加“库存预警”只需在 Service 层写定时任务,不动 Controller。
4. 用户权限:RBAC 最简实现
五张表
user—role—permission,中间表user_role、role_permission。
用户表存username、password(BCrypt)、enabled。SpringSecurity 配置要点
@Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig { @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } }- 方法级鉴权——售药师不能入库
@PreAuthorize("hasAuthority('stock:in')") @PostMapping("/in") public R<Void> stockIn(@Valid @RequestBody StockInDTO dto){ ... }- Vue 侧动态菜单
登录后GET /api/user/info返回permissions: ['stock:in','drug:sale'],前端根据权限过滤路由表,按钮级用v-has自定义指令。
5. 安全性三板斧
- 参数校验:DTO +
javax.validation注解,拒绝 SQL 拼接。 - SQL 注入:MyBatis
#{}占位,杜绝${}拼接。 - 越权访问:RBAC +
@PreAuthorize,并在 XML 层加tenant_id等字段隔离数据。
6. 生产环境避坑指南
数据库连接池
默认HikariCP足够,毕业设计并发低,但一定把minimum-idle设 5 以上,避免 MySQL 8 小时自动断开。静态资源缓存
Vue 打包后dist/放 Nginx,location ~* \.(js|css|png)$ { expires 30d; },回锅肉式提速。配置外置
application.yml里spring.datasource.url用${DB_URL},服务器启动脚本加-DDB_URL=xxx,防止账号密码提交到 GitHub。日志分级
logback-spring.xml里com.example.pharmacy.mapper=DEBUG,生产环境改为 INFO,避免控制台刷 SQL 暴露字段。
7. 进阶思考:把“毕业”做成“产品”
处方审核流
医生端开方 → 药师端审核(状态机:待审核/已驳回/已通过)→ 售药出库。可引入 Flowable 轻量级工作流。库存预警
定时任务扫drug_stock表,近效期 ≤ 30 天自动发邮件给管理员;低于安全库存时钉钉 Webhook 提醒采购。批次追溯
给每盒药贴二维码,出库扫码记录sale_item→drug_stock_id,实现“一批次一去向”,方便召回。接入医保
国家医保接口基于 HTTPS + 数字签名,先把签名工具类封装好,后续直接插拔。
写在最后
整套代码不到 2k 行,却能把“药品字典—库存—销售—权限”串成闭环。答辩时老师若问“如果以后增加连锁门店怎么改?”你可以指着 Service 层说:“门店当租户字段下沉到各表,加一层 Mapper 拦截即可,Controller 不用动。”——这就是低耦合带来的底气。
毕业设计不是终点,把代码推到云服务器,真刀真枪地让家人朋友的药店试用,才是把“学分”升级成“作品”的开始。祝你一次过答辩,早日把项目写进简历,面试时谈笑风生。