先交代一下背景。这段时间不少学弟学妹找我聊毕业设计的事,药品信息管理系统这个题目出现的频率是真的高,从选题角度来看,它比图书管理、学生管理这类经典题目更有行业针对性,业务逻辑又足够典型,评委老师看了也不容易打瞌睡。关键是有完整业务链路——药品分类、供应商、采购入库、销售出库、库存预警,如果你愿意往上加东西,还能挂一个简单的财务统计,整套系统讲起来有层次感。
这篇文章我按毕业设计的完整开发流程来拆:需求怎么梳理、技术栈怎么选、数据库怎么设计、核心功能怎么实现、部署的时候有哪些坑。如果你手里正好拿到一套药品信息管理系统的源码,但不知道怎么跑起来、怎么把它讲清楚,这篇文章就是为你准备的。
1. 项目整体设计与技术选型思路
1.1 这类毕设为什么优先选SSM或Spring Boot
药品信息管理系统说到底就是一个典型的CRUD应用,数据体量不大,并发要求也不高,但它胜在业务结构完整,非常适合用来展示你对Web开发全流程的掌握程度。
在技术选型上,近几年最常见的方案有两套。第一套是经典的SSM(Spring + Spring MVC + MyBatis)配合JSP页面,这是很多学校课程设计还在用的老组合。第二套是Spring Boot + Thymeleaf(或Vue前后端分离)搭配MyBatis Plus,这也是目前我在给学弟学妹做技术预审时的首选建议。
为什么我更推荐Spring Boot?因为它解决了SSM时代最烦人的配置地狱问题。SSM需要手动配置web.xml、spring-mvc.xml、mybatis-config.xml,哪个文件少配一个bean都会莫名其妙报错,很多同学光调配置就能耗掉一两天。Spring Boot通过自动配置把这层复杂度收掉了,你只需要在application.yml里写几个核心参数,项目就能跑起来,省下来的时间应该花在业务逻辑和理解系统架构上。
当然,如果你的学校硬性要求“必须使用SSM框架”,那也别慌,核心思路完全一样,只是把Spring Boot的自动配置换成手动装配而已。后面我会把两种方案在部署上的差异点也讲清楚,保证不管哪条路你都能落地。
1.2 技术栈的核心组成与版本搭配
说到具体的技术栈,我建议你把这套组合记下来,这是目前市面上最稳定的毕业设计技术搭配之一:
后端使用Spring Boot 2.x(推荐2.7.x版本),搭配MyBatis或MyBatis Plus作为持久层框架。Spring Boot 2.7.x避开了Spring Boot 3.x对JDK版本和Jakarta命名空间的要求,与大部分国产教程、开源代码的兼容性也更好。数据库用MySQL 5.7或8.0,如果是老一点的电脑,5.7更省资源;如果是新买的笔记本,直接上8.0也没问题。前端页面用Thymeleaf模板引擎加Bootstrap,这套组合的好处是你不用单独起一个前端工程,打包后就是一个完整的Web应用,部署成本极低。
说句实话,对于毕业设计而言,“能跑起来”永远比“用了多酷炫的技术”更重要。用Vue做前后端分离确实很显技术含量,但如果你对前端掌握的深度不够,光跨域问题就够你喝一壶。Thymeleaf + Bootstrap虽然不是最潮流的方案,但它完全够用,而且答辩时你可以在业务逻辑上多讲十分钟,没必要在最基础的展示层翻车。
另外,开发工具方面我用的是IntelliJ IDEA(社区版就够用),数据库客户端用Navicat。如果你不喜欢IDEA,用Eclipse或VS Code也能完成,核心思路是一致的。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据库设计与核心表结构解析
2.1 从业务场景出发拆解数据模型
药品信息管理系统的数据库设计,是整个项目的命脉。我帮很多同学调过数据库,常见的问题不是表建不出来,而是表的字段设计不合理,比如把药品供应商信息全部塞进药品表里,或者完全不考虑库存变动的追溯需求。
我们先梳理一下业务场景。一个小型药店或医院药房,日常操作有这些:管理员维护药品分类,采购员录入供应商信息和采购单,药品入库后库存增加,销售或领用时库存减少,同时还希望知道哪些药品快过期、哪些药品库存低于安全线。
基于这些场景,核心表至少应该有:管理员(用户)表、药品分类表、药品信息表、供应商表,以及记录库存变动的流水表。很多同学会忽略流水表,认为只要在药品表里改一个库存数字就够了。这是一个常见的错误——如果你只改数字,系统无法回答“这批药是什么时候入库的”“这批药是哪个供应商供的”“每天销售了多少量”这样的问题。增加一张库存变动记录表,把每一次入库出库操作都留痕,整个系统的数据完整性立马上一个档次。
2.2 关键表结构字段设计要点
下面是这套系统里我认为最核心的几张表和字段设计要点,可以直接参照建表:
管理员表(admin),字段包括id、用户名、密码、真实姓名、角色、创建时间。密码字段必须存储加密后的密文,不要存明文,稍后我会讲为什么。
药品分类表(drug_category),字段包括id、分类名称、分类描述、状态。要注意分类名称需要加唯一索引,防止重复创建同名分类导致数据混乱。
药品信息表(drug_info),这张表是核心,字段包括id、药品编码、药品名称、通用名、规格、生产厂家、批准文号、分类id、库存数量、预警库存量、进货价格、销售价格、生产日期、有效期至、创建时间、更新时间。这里有几个设计上的小心思:药品编码要为每种药品生成唯一编码,方便后续扫码或单据引用;库存数量不建议用浮点数,一律用整数,因为药品的最小计量单位是“片/盒/瓶”,用浮点类型容易在边界计算时出现精度问题。
库存变动表(stock_record),字段包括id、药品id、变动类型(入库/出库/报损/盘点)、变动数量、变动前库存、变动后库存、关联订单号或单据号、操作人id、操作时间。这张表是库存模块的灵魂,有了它,系统就能按时间维度做统计分析。
2.3 数据库连接的配置要点
数据库建好后,需要在Spring Boot项目的配置文件中完成连接配置。标准的application.yml核心配置长这样:
yaml复制server:
port: 8080
servlet:
context-path: /
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/drug_manager?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false
username: root
password: 你的数据库密码
thymeleaf:
cache: false
mybatis-plus:
mapper-locations: classpath:mapper/*.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
配置里有几个非常容易出错的地方,我单独拎出来说。
url地址里的characterEncoding=utf8一定要写,否则存储中文时会出现乱码;serverTimezone=Asia/Shanghai必须显式指定,否则MySQL 8.0会报时区错误。useSSL=false也不是随便写的,主要是避免MySQL 8.0默认开启SSL后产生本地环境调试异常。密码一定要注意,如果你本机的MySQL密码有特殊字符,在yml文件里需要转义或者使用单引号包裹,不然启动时会报认证失败。
3. 核心功能模块的代码实现拆解
3.1 登录认证与拦截器
登录模块是系统的门面,也是保安。药品信息管理系统不是开放性网站,普通访客不能随意查看数据,所以要有身份认证和访问控制。
我把登录认证设计成基于Session的方案。用户提交用户名和密码后,后端对密码进行MD5(或加盐处理)加密,然后与数据库中的用户名和密码比对。比对通过后,把用户信息写入Session,同时记录登录时间。
在实际代码里,当用户登录成功时,我们在Controller里这样处理:
java复制@PostMapping("/login")
public String login(String username, String password, HttpSession session, Model model) {
// 对输入的密码进行MD5加密再比对
String encryptedPwd = MD5Util.md5(password);
Admin admin = adminService.login(username, encryptedPwd);
if (admin != null) {
session.setAttribute("admin", admin);
return "redirect:/index";
}
model.addAttribute("error", "用户名或密码错误");
return "login";
}
有些人可能会问,为什么不直接用明文比对?因为现实中很多人会有多个系统共用密码的习惯,一旦数据库泄露,明文密码就会连累用户在别的平台上的账户。虽然毕业设计不追求企业级安全,但用MD5加个密是一行代码的事,答辩时也能作为一个加分亮点讲给评委听,为什么不加呢?
有了登录逻辑,还需要一个拦截器,确保未登录用户无法直接访问系统内部页面。在Spring Boot中,最简方式是实现HandlerInterceptor接口,然后在配置类里注册拦截路径:
java复制@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/login", "/doLogin", "/css/**", "/js/**", "/images/**");
}
}
这里特别要注意排除静态资源路径,如果你把css、js、images也拦截了,页面会出现“裸奔”效果——所有样式全部丢失,看起来就像网页坏了一样。我在帮人排查项目时,这种低级问题占了不小比例。
3.2 药品信息管理的增删改查实现
药品信息管理页是系统的主页面,包含表格展示、条件查询、新增药品、编辑药品、删除药品几个操作。用MyBatis Plus来做的话,CRUD的代码量会非常少,这也是我推荐用MyBatis Plus的原因之一。
列表查询加条件分页,是这类系统里最标准的写法。前端通过表单提交药品名称和分类等查询条件,后端用MyBatis Plus的LambdaQueryWrapper动态拼接查询条件,最终返回一个分页对象:
java复制public Page<DrugInfo> pageQuery(int pageNum, int pageSize, String name, Long categoryId) {
Page<DrugInfo> page = new Page<>(pageNum, pageSize);
LambdaQueryWrapper<DrugInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.like(StringUtils.hasText(name), DrugInfo::getName, name)
.eq(categoryId != null, DrugInfo::getCategoryId, categoryId)
.orderByDesc(DrugInfo::getCreateTime);
return drugInfoMapper.selectPage(page, wrapper);
}
你有没有发现这一小段代码里面有几个技巧点:like方法传入的第一个参数是一个boolean表达式,只有条件成立时才拼接该查询条件。如果name为空字符串,就不拼接模糊查询;如果categoryId为null,就不拼接等值查询。这样条理清晰,也避免了手写SQL时那一大堆“if name != null and name != ''”的重复判断。
新增和编辑在实现时要注意一个细节:药品编码一旦生成,就应该禁止修改。因为编码可能关联着后续的采购单、库存流水,如果允许随意修改,历史数据会跟着乱掉。所以在编辑页面里,药品编码字段我设置成只读,前端直接禁用,后端在更新时也不接收这个字段的修改。
删除逻辑也要谨慎。药品信息表被库存记录表外键引用,如果直接物理删除药品,流水表里就会留下悬空引用。在实际系统中,我推荐采用逻辑删除——在药品表加一个deleted字段,默认值为0,删除操作改为将deleted置为1,查询时自动过滤已删除的记录。这样既保证了数据可追溯,又不会破坏外键关系。
3.3 入库出库与库存预警逻辑
库存模块是药品信息管理系统里最出彩的部分,也是最值得在答辩时展开讲的部分。
先说入库。入库操作可以走一条简化流程:管理员选择药品、填写入库数量、保存后系统执行两件事——更新药品表的库存数量,同时往库存变动表插入一条入库记录。为了保证这两步操作的一致性,必须加上事务控制,否则可能出现库存更新了但流水没记录(或反过来)的数据不一致状态。
在Spring Boot中加事务特别简单,在Service方法上标一个@Transactional注解就行:
java复制@Transactional(rollbackFor = Exception.class)
public void stockIn(Long drugId, Integer quantity, Long operatorId) {
DrugInfo drug = drugInfoMapper.selectById(drugId);
int beforeStock = drug.getStock();
int afterStock = beforeStock + quantity;
drugInfoMapper.updateStock(drugId, afterStock);
StockRecord record = new StockRecord();
record.setDrugId(drugId);
record.setType("IN");
record.setQuantity(quantity);
record.setBeforeStock(beforeStock);
record.setAfterStock(afterStock);
record.setOperatorId(operatorId);
stockRecordMapper.insert(record);
}
我见过不少同学的库存更新是用“先查询出来,在Java代码里加个数字,再整体update”的方式做的,在高并发场景下会有并发覆盖问题。因为两个请求同时读到库存100,各自加10,最后都会把库存更新成110,实际应该变成120。虽然毕业设计几乎没有并发压力,但如果在答辩时主动讲出“我这里使用乐观锁/数据库原子操作来保证库存数据的正确性”,绝对会让评委眼前一亮。
出库逻辑和入库是对称的,只是把加号换成减号,同时更新前要检查库存是否充足,不足则抛出业务异常提示。
库存预警怎么做呢?思路也很直接:药品表里有一个预警库存量字段,当库存数量低于预警值时,药品列表页就显示“库存不足”的红色标签。更进一步的方案是通过定时任务每天扫描一次低库存药品,把预警信息写入一张通知表,管理员登录后在首页看到消息提醒。用Spring Boot的@Scheduled注解就能实现这个定时任务,代码量也不大,但呈现出来的效果会非常丰富。
4. 部署教程:从零到可访问的完整流程
4.1 环境准备与配置
部署是整个毕设过程中最容易出问题的阶段,我把每一步都写清楚,你跟着做就行。
第一步,安装JDK。如果你的后端用的是Spring Boot 2.x,装JDK 8或JDK 11都行。安装完成后,关键是要配好环境变量。
在Windows下配置JDK环境变量,分三步:首先创建一个JAVA_HOME变量,值是JDK安装目录,比如C:\Program Files\Java\jdk1.8.0_202;然后在Path变量里添加%JAVA_HOME%\bin;最后在命令行窗口输入java -version,能看到版本信息就表示配置成功。
很多人在这步会犯一个错,把JAVA_HOME的值写成了jre目录或者写成了包含bin的完整路径,这样命令行会找不到java命令。记住,JAVA_HOME是JDK的根目录,bin目录是在Path里拼接进去的。
第二步,安装MySQL。建议直接下载MySQL Installer,一路Next,中间只要注意设置root密码时记住密码就行。安装完成后,打开命令行,输入mysql -u root -p回车,输入密码能进入MySQL命令行界面就说明服务正常。
第三步,安装IDEA和Navicat。IDEA社区版免费,功能足够写Spring Boot项目;Navicat是数据库管理工具,用来导入数据库脚本和执行SQL语句。如果你不想用Navicat,用MySQL自带的Workbench或者直接命令行导入SQL脚本也完全可以。
4.2 数据库导入与项目运行
环境准备好之后,开始导入数据库。打开Navicat,连接到本机MySQL服务,右键创建数据库,数据库名建议和源码里的配置保持一致,比如drug_manager,字符集选utf8mb4。创建完成后,选中该数据库,点击“运行SQL文件”,选择源码目录下提供的sql文件,点击开始,等待导入完成即可。
导入后,可以在左侧看到系统所有的表。建议做一件事:手动在管理员表里插入一条测试账号,并将密码设置为MD5加密后的密文。这个加密串怎么生成?你可以在Java代码里写个main方法,调用工具的加密函数,或者用在线MD5工具生成。比如密码为admin123,经过MD5加密后是0192023a7bbd73250516f069df18b500。把这个串更新到数据库里,系统启动后就能用admin/admin123直接登录了。
接下来用IDEA打开项目源码。打开后先别急着运行,右下角会提示Maven需要导入依赖,点击“Enable Auto-Import”等待依赖下载完成。这一步受网络环境影响很大,如果下载慢,可以配置阿里云的Maven镜像,在maven的settings.xml文件的mirrors节点下添加镜像地址。等控制台输出“BUILD SUCCESS”后,就可以启动应用了。
最后一步是启动Application类中的main方法。观察日志,看到类似“Tomcat started on port(s): 8080”的信息,就说明启动成功。打开浏览器,输入http://localhost:8080就能看到登录页。
如果你的项目是传统的SSM结构,部署思路一样,只是多了一步:需要把项目打war包,然后放到Tomcat的webapps目录下,启动Tomcat服务后通过http://localhost:8080/项目名来访问。Spring Boot项目则是内嵌了Tomcat,直接运行jar包即可,这是两者在部署层面最重要的差异。
4.3 直接启动运行jar包的快捷方案
如果你的源码是一个已经打包好的jar包,部署就更简单了。确认环境已经安装了JDK,然后在jar包所在目录打开命令行,执行:
bash复制java -jar drug-manager.jar
想要后台运行不占用命令行窗口,可以写一个批处理脚本,或者使用nohup命令(Linux环境下)。Windows环境下也可以创建一个start.bat文件,内容就是上面这一行命令。需要注意的是,jar包运行前依然要保证MySQL已启动,数据库已导入,配置文件里的数据库账号密码正确,否则启动时会报数据库连接失败。
5. 常见问题排查与官方级避坑清单
5.1 启动报错类问题速查表
部署过程中最常见的报错就那几类,我把它们整理成了一张表,你遇到问题时直接对照检查:
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 启动报java.sql.SQLException: Access denied for user | 数据库用户名或密码错误 | 检查application.yml中的username和password是否与MySQL实际账号一致 |
| 启动报Unknown database | 数据库中不存在对应的库名 | 在MySQL中创建同名数据库并导入SQL脚本 |
| 启动报Public Key Retrieval is not allowed | MySQL 8.0的认证插件问题 | 在url末尾加allowPublicKeyRetrieval=true |
| 页面中文乱码 | 数据库字符集或连接url未配置 | 数据库建库时选utf8mb4,url加characterEncoding=utf8 |
| 404页面无法访问 | context-path配置问题或未配置 | 检查server.servlet.context-path是否与实际访问路径一致 |
| 端口被占用 | 8080端口被其他程序占用 | 换端口或将占用进程关闭 |
5.2 数据库连接失败与Redis冲突的野路子
除了上面这些常规问题,还有两个发生率很高但容易被忽视的坑。
第一个坑是:项目本地跑不起来,显示数据库连接失败,但你在Navicat里用同样的账号密码又能正常连接。这种诡异现象,八成是配置文件里密码前后有空格,或者yml文件的缩进出了问题。YAML对缩进极其敏感,一个Tab键换来的可能是一个小时的排错时间。所以如果你复制的是网上的配置模板,务必用文本编辑器显示所有字符,检查有没有隐藏的空格。
第二个坑是:启动时提示端口被占用,有时不是真的被别的软件占用,而是上一次运行Spring Boot的后台进程没有完全结束。在IDE里点红色停止按钮后,应用进程并没有退出,你再次点击启动就会冲突。解决方式是打开任务管理器,找到对应的Java进程并结束,或者直接换一个端口。
5.3 将项目源码变成自己的项目
最后说一个非常重要的经验:拿到一套源码后,不要想着原封不动交上去,一定要自己“消化”一遍,让项目真正变成你自己的东西。
具体怎么做呢?第一,把项目的包名、类名、页面显示的文字信息都过一遍,把里面的统一改成你自己的标识信息。第二,在核心模块里做一点微创新,比如给入库增加一个“供应商选择”的下拉框,给药品列表增加一个“按有效期排序”的功能,这些改动不复杂,但能在答辩时让你有话可说。第三,把数据库的sql脚本重新执行一遍,理解每张表的用途和表之间的关系,这样才能应对评委的临时提问。
我见过太多同学因为对项目不够熟悉,被评委问一句“这个字段为什么这么设计”就卡住了。与其在答辩前焦虑,不如花一个晚上把整个系统的代码和表结构从头到尾读一遍,把自己代入到开发者的视角去理解每一个设计决策,这才是毕业设计真正的价值所在。
我个人的经验是,主动把项目里的1-2个细节改得比原始版本更好,哪怕只是优化一条SQL查询语句的写法,都可以在答辩时当成“本项目的一个优化点”来讲。这件事既训练了读代码的能力,也能让老师看出你确实参与了开发,一举两得。
