SpringBoot+Vue 医院药品管理系统,这个标题在毕设、课设资源里出现的频率相当高,Java+MySQL 的全栈组合,功能覆盖药品信息管理、库存监控、采购入库、处方销售这些典型业务。不管是拿来交毕业设计、课程作业,还是作为学完 Spring Boot 和 Vue 之后练手的第一个完整项目,它都是一个非常合适的选择。
这几年我带过不少同学的毕业设计和课程项目,反复接触过类似的管理系统。这类项目最有价值的一点是:它不是一个简单的"增删改查 Demo",而是真实医院药品流转的缩略版。你从中能学到的不只是代码,还有业务单据怎么流转、库存如何控制、角色权限怎么区分,这些在课堂作业和拼凑的小练习里很难完整遇到。本文会从整体架构拆解、数据库设计、后端关键逻辑、前端页面实现、本地运行操作、常见问题排查这几个维度,把这类系统的完整脉络讲清楚。
1. 先弄明白:这套系统到底解决什么问题
1.1 医院药品管理的核心业务链路
医院药剂科每天面对的工作远不只是"进货、卖药"这么简单。一支抗生素从供应商送抵药库,到它最终被门诊医生开给患者,中间要经历验收入库、库存存储、效期监控、药房领药、处方审核、发药出库、用药记录归档这好几个环节。任何一个环节出错,轻则库存对不上账,重则过期药品流入临床。
这类系统就是为这个过程服务的。它把药品从"进院"到"离院"的全生命周期管理起来,核心业务链路大致是:药品资料建档 → 供应商采购 → 入库审核 → 库存更新 → 药房/临床科室领用 → 门诊处方销售 → 库存扣减 → 效期与库存预警。此外还有用户管理、登录鉴权、操作日志、统计报表等支撑性功能。
在做项目拆解之前,建议先把上面这条链路画一遍。你会发现所有功能模块本质上都是这条链上的一个节点,理解了业务流,代码再复杂也不会走偏。
1.2 技术栈选型的真实原因
市面上同类系统不少,有的用 SSM(Spring + Spring MVC + MyBatis),有的用 Spring Boot + Thymeleaf 服务端渲染,而SpringBoot + Vue + MySQL这套组合之所以在毕设和课设项目中成为主流,有它的现实原因。
Spring Boot 最大的价值是"自动配置 + 约定优于配置",它帮开发者省掉了大量 XML 配置和繁琐的依赖管理。用 Spring Boot 写后端,只需要一个标注了@SpringBootApplication的入口类,配合若干 Controller、Service、Mapper 就能跑起来一个完整的 Web 服务。配合 MyBatis-Plus,单表的增删改查基本不用手写 SQL,开发效率明显高于传统的 Spring + Spring MVC。
前端选择 Vue,是因为 Vue 的学习曲线在三大前端框架里相对平缓,而且 Vue 的单文件组件结构非常直观。模板、脚本、样式写在一个.vue文件里,对于 Java 后端同学来说非常容易上手。再搭配 Element UI 或者 Element Plus 这样的组件库,表格、表单、弹窗、分页器几乎都是现成的。一个基础还算扎实的同学,两周时间可以完整做出来,毕设进度压力不会太大。
数据库选 MySQL 则是最稳的选择,它足够成熟、社区资料多、Navicat 等图形化工具齐全,部署和学习成本在校园网环境里基本没有门槛。
1.3 这类系统的三重用途
毕业设计看重的是功能完整度 + 技术覆盖面 + 论文可写性。药品管理系统涵盖了权限管理、业务单据、库存预警、统计图表,这些都能拆成独立章节写进论文,答辩时也容易讲清楚。
课程设计看重的是结构清晰、调试方便、一人能完成。这种规模的系统刚好卡在一个"不太大也不太简单"的点上,同理,不容易出现代码多到看不完的情况,也不会被老师说工作量不足。
日常练手看重的是学到了什么。通过这套系统,你能把 Vue 前端、Axios 请求、Spring Boot 接口开发、MyBatis 持久层、MySQL 表设计、JWT 鉴权这些知识串成一个闭环,这是做几百道练习题换不来的经验。
2. 系统模块拆解与数据库设计
2.1 功能模块怎么划分最合理
动手写代码之前,先按业务域把系统切成几个模块。参考我见过的大多数项目,一个功能完整、答辩经得起问的药品管理系统通常包含以下几个模块:
| 模块 | 核心功能 | 对应的典型页面 |
|---|---|---|
| 系统管理 | 用户登录、账号管理、角色权限、修改密码、操作日志 | 登录页、用户列表、角色分配 |
| 药品信息管理 | 药品档案的增删改查、药品分类、处方药/非处方药标记 | 药品列表、药品详情、新增/编辑弹窗 |
| 库存管理 | 入库登记、出库发放、库存查询、库存流水、效期提醒 | 库存列表、入库单、出库单、预警列表 |
| 供应商管理 | 供应商档案、联系方式、历史供药记录 | 供应商列表、供应商编辑 |
| 采购与入库 | 采购订单生成、到货审核确认、自动写库存流水 | 采购单创建、待收货列表、入库确认 |
| 处方/销售 | 门诊处方录入、销售记录、销售明细统计 | 销售开单、销售记录查询 |
| 统计报表 | 月度出入库统计、销售金额排行、库存周转分析 | 图表看板、排行榜 |
功能模块的划分要遵循一个原则:每个模块之间的关系要单向依赖,避免循环引用。比如药品库存模块可以依赖药品信息和供应商模块,但供应商模块就不要反向去查库存表的统计结果,否则后面维护起来非常头疼。
模块划分好之后,前后端的菜单结构、路由结构、权限控制点基本都定下来了。后端的包结构建议按模块而不是按技术层来建包,也就是先建controller、service、mapper、entity四大层,再在层内按业务模块分包,比如entity/drug、entity/purchase、entity/system。这样代码量大了之后依然能找到头绪。
2.2 核心数据表结构与设计思路
数据库设计决定了这个项目的质量上限。很多同学拿着别人的源码导入数据库,发现表之间外键散落、字段类型混乱,改一个功能牵连出一堆 Bug。我按实际项目的标准,梳理出最核心的几张表:
用户表(sys_user)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| username | varchar(50) | 登录名,唯一 |
| password | varchar(100) | BCrypt 加密后的密码 |
| real_name | varchar(50) | 姓名 |
| role | varchar(20) | 角色标识:admin / pharmacist / buyer |
| status | tinyint | 是否禁用 |
| create_time | datetime | 创建时间 |
药品信息表(drug_info)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| drug_code | varchar(30) | 药品编码,唯一 |
| drug_name | varchar(100) | 药品名称 |
| category_id | bigint | 药品分类ID |
| specification | varchar(50) | 规格,如 0.25g*24粒 |
| unit | varchar(10) | 单位:盒/瓶/袋 |
| manufacturer | varchar(100) | 生产厂家 |
| prescription_flag | tinyint | 是否处方药,1是0否 |
| retail_price | decimal(10,2) | 零售价 |
| stock_threshold | int | 库存预警阈值 |
| status | tinyint | 上下架状态 |
库存表(drug_stock)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| drug_id | bigint | 关联药品ID |
| batch_no | varchar(50) | 批次号 |
| quantity | int | 当前数量 |
| expire_date | date | 有效期至 |
| supplier_id | bigint | 来源供应商 |
| warehouse_pos | varchar(50) | 库位 |
这里单独立一张库存表,而不是把"库存数量"直接写在药品表里,是因为药品存在批次的概念。同一款药品不同批次的有效期不同、采购价格不同,混在一起没法做效期管理。这也是毕设答辩时一个非常容易被追问的设计亮点。
销售记录表(sale_record)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| record_no | varchar(30) | 销售单号 |
| drug_id | bigint | 药品ID |
| quantity | int | 销售数量 |
| amount | decimal(10,2) | 销售金额 |
| user_id | bigint | 操作人 |
| sale_time | datetime | 销售时间 |
加上供应商表、分类表、采购入库单表、库存流水表,总共七八张核心表就可以支撑一套完整系统了。表设计时注意统一主键策略,推荐ASSIGN_ID雪花算法,不要用数据库自增,因为后续做数据迁移和分布式部署时自增主键容易冲突。
2.3 药品库存与有效期关系的细节
这是一个容易踩坑的点。如果你只在药品表里放一个总库存数量字段,遇到同一药品有两个入库批次、有效期各不相同时,就没办法区分哪些接近过期。而医院药房实际发药时是有原则的:近效期先出、先进先出。
所以在设计库存流水(stock_flow)时,除了记录"入/出/调整"类型、数量、经办人、时间之外,还要额外记录批次号、关联的单据号。这样拿着批号就能反查某一批药进出情况,效期预警按expire_date倒排,把 90 天内过期的批次单独拉出来。
如果部分源码中药品表直接存总库存也能满足基本演示,但答辩时如果老师问"同一药品两个批次如何管理",只靠一个数字字段是答不上来的。所以我在系统设计里强烈建议库存用批次表存。
3. 后端核心逻辑:从登录鉴权到库存扣减
3.1 工程结构与依赖配置
一个规范的 Spring Boot 后端工程,至少要分成这些包:
com.example.hospital ├── config // 配置类、拦截器、跨域配置 ├── controller // 接口层 ├── service // 业务逻辑层 ├── mapper // 数据访问层 ├── entity // 实体类 ├── common // 通用工具类、统一返回结果、异常处理 └── HospitalApplication.java // 启动类依赖管理建议直接使用 Spring Initializr 生成,然后手动添加 MyBatis-Plus、MySQL 驱动、JWT 相关依赖、Lombok。核心依赖如下:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>MyBatis-Plus 的使用值得多说两句。它内置了BaseMapper<T>,把单表的增删改查、分页查询、条件构造器都封装好了。比如查某个分类下的所有药品,传统 MyBatis 要写<select>标签和对应 SQL,用 MyBatis-Plus 直接这样写:
LambdaQueryWrapper<DrugInfo> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(DrugInfo::getCategoryId, categoryId); wrapper.eq(DrugInfo::getStatus, 1); List<DrugInfo> list = drugInfoMapper.selectList(wrapper);Lambda 写法能避免硬编码字段名,字段名改了也会在编译期暴露问题,比手写字符串安全。这是提升开发效率最明显的一环。
3.2 JWT 登录鉴权和角色权限控制
系统里不同角色看到和能操作的菜单不一样:管理员可以管理用户、查看全部数据,药剂师可以开销售单,采购员负责录入采购和入库。所以登录校验之后还要做权限控制。
登录接口的处理逻辑很简单:
- 根据用户名查用户表;
- 用 BCrypt 校验密码(不要让自己成为不会加密的反面教材);
- 校验通过,生成 JWT Token,将
userId、role写进 token 的 claims; - 返回 token 给前端,前端后续请求在请求头
Authorization里带上。
String token = Jwts.builder() .setSubject(user.getUsername()) .claim("userId", user.getId()) .claim("role", user.getRole()) .setExpiration(new Date(System.currentTimeMillis() + 2 * 60 * 60 * 1000)) .signWith(SignatureAlgorithm.HS256, secretKey) .compact();光生成 token 还不够,还要写一个拦截器或过滤器,在所有接口请求进来时做校验。我习惯在HandlerInterceptor里做,先放行登录接口,再校验其他请求的 token:
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (request.getMethod().equals("OPTIONS")) { return true; } String token = request.getHeader("Authorization"); if (token == null || !token.startsWith("Bearer ")) { throw new BusinessException("未登录或登录已过期"); } // 解析并校验 token,将 userId 放入 request attribute return true; }关于密码存储,这里强烈建议使用 BCrypt。它每次加密的结果是随机的,但校验结果一致,就算数据库泄露,反推明文密码的成本也极高。代码实现上用 Spring Security 里的BCryptPasswordEncoder或者 jBCrypt 库都可以。
3.3 库存扣减的并发安全处理
这是整个系统里最有技术含量的地方,也是最能体现"你有没有真正理解业务"的点。
我们先看一个错误示范。很多同学写销售出库是这样:
DrugStock stock = stockMapper.selectById(stockId); if (stock.getQuantity() >= quantity) { stock.setQuantity(stock.getQuantity() - quantity); stockMapper.updateById(stock); }先查出来再修改。如果两个请求同时操作同一批次库存,都查出来quantity = 10,各自减 5,两边都认为扣减成功,最终数据库可能只变成 5,而不是 0。这在并发访问量稍高的系统里必然出问题。
正确做法是用一条带条件的 SQL 完成扣减,这也是我在项目里采用的方案:
int rows = stockMapper.deductStock(drugId, batchNo, quantity); if (rows == 0) { throw new BusinessException("库存不足或批次不存在"); }对应的 Mapper SQL:
UPDATE drug_stock SET quantity = quantity - #{quantity} WHERE drug_id = #{drugId} AND batch_no = #{batchNo} AND quantity >= #{quantity}UPDATE语句自带行锁,两个事务同时执行时,第二个事务会等待第一个事务提交后,再基于最新数据判断quantity >= ?是否成立。如果第二个请求扣减数量超过了剩余库存,影响行数就是 0,业务层抛异常回滚即可。这套方案非常简单,不需要引入 Redis 分布式锁,但对单机应用已经足够。
同时,每次扣减后别忘了写一条库存流水。流水记录的不仅是操作行为,也是日后对账、排查问题的唯一线索。这个环节在答辩时提出来,老师基本都会认可。
3.4 药品有效期预警与库存阈值报警
预警功能是药品管理系统区别于普通商品管理系统的标志之一,学生时代很容易忽略,但医院药剂科非常看重。
效期预警的实现逻辑是:查询所有库存批次,把expire_date距离当前日期小于 90 天的批次筛选出来。90 天这个阈值建议直接做成配置,不要写死在 SQL 里。SQL 大概是这样:
SELECT * FROM drug_stock WHERE expire_date BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 90 DAY)库存阈值报警是在药品信息表里设置一个stock_threshold字段,比如某药品低于 50 盒就报警。统计当前库存小于阈值的药品时,可以用一条带子查询的语句:
SELECT d.*, s.total_quantity FROM drug_info d LEFT JOIN ( SELECT drug_id, SUM(quantity) AS total_quantity FROM drug_stock GROUP BY drug_id ) s ON d.id = s.drug_id WHERE s.total_quantity < d.stock_threshold把这两类预警查出来之后,管理系统里做一个醒目的"库存预警"菜单,前端进入页面就触发一次查询,有预警数据时在导航栏展示红点或徽标数。这样系统一下子就从一个"记录工具"升级成了"管理工具"。
4. 前端 Vue 实现:页面交互与数据对接
4.1 前端工程结构与路由划分
前端部分我见过两种方案:一种是纯 Vue + Vue Router + Vuex/Pinia + Axios,自己手写全部页面;另一种是直接使用若依(RuoYi)这类开源脚手架做二次开发。先说结论:自己从零写前端更推荐,因为你得真正理解页面数据是怎么来的,而不是一直停留在"改别人模板"的层面。
推荐的前端工程结构:
src ├── api // 按模块封装的请求接口 │ ├── drug.js │ ├── stock.js │ ├── sale.js │ └── user.js ├── assets // 静态资源 ├── components // 公共组件 ├── layout // 主界面布局,包含侧边栏和顶栏 ├── router // 路由配置 ├── store // 全局状态管理 ├── utils // 请求封装、工具函数 ├── views // 页面组件 │ ├── login │ ├── drug │ ├── stock │ ├── purchase │ └── report ├── App.vue └── main.js路由配置要按照后端模块一一对应。比如:
{ path: '/drug', component: Layout, meta: { title: '药品管理', roles: ['admin', 'pharmacist', 'buyer'] }, children: [ { path: 'list', component: () => import('@/views/drug/list.vue'), meta: { title: '药品列表' } }, { path: 'category', component: () => import('@/views/drug/category.vue'), meta: { title: '药品分类' } } ] }路由的meta.roles是后端返回的角色标识,前端在路由守卫里判断用户角色是否有权限进入。光有前端路由守卫其实不够,后端接口也要做权限校验。前端控制只是把没权限的菜单隐藏掉,真正的安全防线必须放在后端,这个原则在答辩时被问到安全问题可以着重讲。
4.2 药品管理页面的增删改查
药品列表页是整个前端最核心也最典型的页面,它几乎涵盖了所有你会在项目中反复使用的交互方式:条件查询、分页表格、新增弹窗、编辑回显、删除确认。
具体实现时,页面加载时调用getDrugPage(params)接口,返回的分页数据包含records和total。表格绑定records,分页组件绑定total,切换页码或改变每页数量时重新调用接口。
新增/编辑药品用 Dialog 弹窗,表单字段和后端实体对齐。这里有个小建议:表单校验规则可以用 Element UI 内置的rules,比如必填项、数字范围、价格精度。提交前做一次整体校验,能拦住大部分低级错误,减少后端处理"脏数据"的负担。
删除操作用this.$confirm弹出确认框,确认后调用删除接口,成功后刷新列表。这类看似简单的内容,很多同学会漏掉删除后的关联处理:比如直接删除一个药品,而库存表和销售记录表里还引用着这个药品ID,就会产生脏数据。所以合理的做法是:有库存或者有销售记录的药品不能物理删除,只能用status字段做逻辑下线。这个细节非常能够体现一名开发者的工程意识。
4.3 Axios 封装与跨域处理
如果前端代码里每个页面都写axios.get(url).then(...),那叫能用,不叫工程。实际项目里我会先在utils/request.js里做 Axios 实例封装:
import axios from 'axios' import { Message } from 'element-ui' import router from '@/router' const service = axios.create({ baseURL: process.env.VUE_APP_BASE_URL, timeout: 10000 }) // 请求拦截器:自动携带token service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = 'Bearer ' + token } return config }) // 响应拦截器:统一处理返回状态码 service.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { Message.error(res.msg) if (res.code === 401) { router.push('/login') } return Promise.reject(res) } return res }, error => { Message.error(error.message) return Promise.reject(error) } ) export default service这样每个 API 模块只需要这样调用:
import request from '@/utils/request' export function getDrugPage(data) { return request({ url: '/api/drug/page', method: 'post', data }) }开发环境的跨域处理有两种常用办法。第一种是后端写@CrossOrigin或者全局 CORS 配置;第二种是前端用 devServer 的 proxy,把/api开头的请求代理到http://localhost:8080后端地址:
// vue.config.js module.exports = { devServer: { port: 3000, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } }我在实际开发中更推荐用前端代理,而不是在后端开启跨域。因为后端开启跨域等于允许所有来源访问,而开发环境代理只存在于本地开发阶段,部署上线后由 Nginx 统一转发,后端接口可以保持同源,安全策略更清晰。
4.4 可视化统计与告警展示
药品管理系统一般会加一个统计看板,展示销售趋势、库存预警数量、药品分类占比。这些图表用 ECharts 实现比较方便。
销售趋势图的逻辑是:后端按天聚合销售金额,返回一组{ date, amount }数据,前端把数据放进去:
const chart = echarts.init(document.getElementById('saleChart')) chart.setOption({ xAxis: { data: dates }, yAxis: {}, series: [{ type: 'line', data: amounts, areaStyle: {} }] })库存预警的数字展示则可以用一个 Statistic 组件,把warningCount以醒目的红色数字放在看板右上角,点击可跳转到库存预警列表。
这类视觉内容也是答辩展示时的加分项。老师通常不会逐行看代码,但看到界面上的图表和数字会形成第一印象:这个项目不是空壳。
5. 本地运行实操:从环境准备到项目跑通
5.1 环境准备与工具版本搭配
把项目从源码变成能跑的系统,这一趴写给完全没起步的新手,老手可以直接跳到 5.3。
推荐的环境版本如下:
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| JDK | 1.8 或 11 | Spring Boot 2.x 首选 |
| Maven | 3.6+ | 管理后端依赖 |
| MySQL | 5.7 或 8.0 | 项目数据库 |
| Node.js | 14 或 16 | Vue2 项目的运行环境 |
| npm / yarn | npm 6+ 或 yarn | 前端包管理 |
这里有一个很重要的经验:不要用最新的 JDK 版本跑 Spring Boot 2.x 项目。JDK 17 或更高版本虽然能启动基础项目,但会遇到一些反射访问受限、依赖不兼容的问题,对于学习和调试来说毫无必要。Spring Boot 2.7 配 JDK 8 或 11 是最稳的组合。
5.2 数据库初始化与配置文件修改
拿到项目源码后,通常在根目录或sql目录下能找到init.sql或hospital_db.sql这样的文件。用 Navicat 或命令行执行:
mysql -u root -p < hospital_db.sql执行完以后确认数据库hospital_db存在,并且能看到sys_user、drug_info、drug_stock等表和数据。
然后修改后端的application.yml,把数据库地址、用户名、密码改成你自己的:
server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/hospital_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456这里serverTimezone=Asia/Shanghai一定要加,否则 MySQL 8.0 会报时区相关的错误。如果你的数据库密码是空,password那行留空也可以,但不建议这么做。
5.3 后端启动步骤
在项目根目录(包含pom.xml的目录)打开终端,执行:
mvn spring-boot:run第一次启动会下载大量依赖,可能需要几分钟到十几分钟,取决于网络情况。启动成功的标志是控制台出现Started HospitalApplication in x.x seconds,并且没有红色报错。
如果mvn命令不可用,用 IDEA 打开项目,等待右下角 Maven 依赖导入完成后,直接运行启动类也可以。建议直接使用 IDEA 的 Maven 面板,点击spring-boot:run,比命令行更方便调试。
启动后可以用浏览器访问http://localhost:8080,如果配置了欢迎页则能看到效果,否则访问任意接口路径会得到 JSON 或 404。至少能证明程序没有崩。
5.4 前端启动与页面验证
前端代码在vue或frontend目录下。进入目录,先安装依赖:
npm installnpm install 是新手最容易卡住的环节。如果下载极慢或者报错,换成国内镜像:
npm config set registry https://registry.npmmirror.com依赖安装完成之后直接启动:
npm run serve默认情况下 Vite 或 Vue CLI 会起一个本地服务(端口一般在 8080 或 3000),你需要在vue.config.js里把代理目标地址指到后端的8080,就像 4.3 节写的那样,避免跨域问题。
启动成功后访问前端地址,用初始账号登录。常见的初始账号密码会在项目的README.md里说明;如果源码里没有,直接在sys_user表里看数据,通常就是admin/admin123。登录后检查药品列表能否正常加载,新增一条药品再刷新,能出现就说明前后端联调成功。
6. 常见问题排查与毕设答辩加分点
6.1 高频问题排查表
把我在指导项目中遇到的最典型问题和解决方式整理成下面的表,很多坑都是反复出现的高频问题。
| 问题现象 | 可能原因 | 解决办法 |
|---|---|---|
| 前端页面能打开,但列表数据一直加载不出来 | 后端没启动 / 接口代理路径配错 | 检查后端控制台,看 8080 是否在监听;检查/api代理是否指对了 |
| 后端报 Access denied for user | 数据库用户名或密码写错 | 用 Navicat 验证账号能否登录,再去改 application.yml |
| 前端报 401 Unauthorized | token 过期或未携带 | 用浏览器开发者工具查看请求头,确认 Authorization 字段存在 |
| MySQL 提示 Unknown database | 没导入 SQL 或导错库 | 确认init.sql完整执行,并确认连接的是同一个库名 |
| 控制台报端口占用 | 8080 或 3000 被其他进程占用 | Windows 用 `netstat -ano |
| Maven 依赖下载报红 | 本地仓库缺包 / 镜像未配 | 配置阿里云 Maven 镜像,IDEA 中File -> Settings -> Maven -> Mirror添加 |
| 中文乱码 | 数据库字符集不对 | 建库时指定DEFAULT CHARSET utf8mb4,连接 URL 加characterEncoding=utf8 |
| 分页查询返回 total 始终为 0 | 忘记配置 MyBatis-Plus 分页插件 | 在 config 类中添加PaginationInnerInterceptor |
6.2 润毕设/课设时的几个切入点
如果这个项目拿来当毕业设计,不建议直接原封不动交上去。答辩老师对热门项目的源码往往非常熟悉,翻来覆去就是那几套,不加点自己的东西很容易被质疑工作量。
我建议从几个方向做增量优化:
第一个方向是增加多级审核流程。采购入库单不直接生效,而是先创建"待审核"状态,由管理员或药房主任审核后方可入库。这个改动需要加审核状态字段、审核时间字段、审核人字段,业务复杂度立刻上升一个档次,论文里可以写一章"业务流程设计与审批模型"。
第二个方向是增加报表模块。除了简单的查询列表,用 ECharts 按药品分类做月度销售雷达图、按供应商做采购占比饼图。后端用日期函数做分组统计即可,前端只用 ECharts 呈现,履历上写"数据可视化模块",观感好很多。
第三个方向是引入 Redis 缓存。把登录 token 存到 Redis,设置过期时间,顺便实现登录验证码的存储。这个改动虽然不大,但涉及 Redis 的使用,能在技术栈上加一分。注意,如果你的毕设只要求 Java + MySQL,引入 Redis 前先确认一下老师的要求,不要额外给自己挖坑。
第四个方向是优化密码找回与个人信息管理。增加修改密码的强校验流程,例如旧密码校验、密码强度校验,这类细节在课设答辩时很容易获得好评。
6.3 这套系统再扩展还能怎么玩
药品管理系统虽然在业务上看起来比较"传统",但它的扩展潜力不小。可以把它往移动端方向扩展,开发一个小程序或 H5 版本的药师端,实现移动审批和库存盘点;也可以往数据分析方向扩展,除了销售金额,增加药品滞销分析、效期消耗率、科室领药频次统计等指标,让系统从"记录工具"变成"决策工具"。
如果你对微服务感兴趣,可以把用户模块、药品模块、库存模块拆开,用 Spring Cloud Alibaba 的方式做服务化改造。改动幅度很大,但对学习和简历讲述来说是一次完整的架构升级锻炼。做这一步之前先想想自己的时间投入,如果是在准备论文的关键阶段,不建议轻易动刀。
我在实际带项目的过程中发现,很多同学把大部分时间耗在"改 bug"和"倒腾环境"上,反而没有时间去深究那些能体现设计能力的地方,比如严格的库存批次管理、逻辑删除策略、权限模型的角色粒度划分。而答辩聊得深的,往往不是功能多不多,而是某个关键业务问题你是怎么思考的。这套系统从头到尾走一遍,恰好能把这些问题都覆盖到。
最后分享一个很实用的小技巧:项目开发过程中,每次启动前把application.yml里数据库密码错填一次,强迫自己看一遍报错日志。连续几次下来,你对 Spring Boot 启动链路上的常见报错会变得非常敏感,以后再遇到类似排错问题,基本一眼就能定位。这种动手排查的能力,才是做这类项目沉淀下来最值钱的东西。