SpringBoot药店管理系统:Java项目源码启动与前后端联调实践 📅 发布时间:2026/9/14 8:12:30 👁 浏览次数: 简介一套面向计算机、电子信息工程等专业学生的基于SpringBoot的药店管理系统源码适合作为高分毕业设计、课程设计或期末大作业。压缩包内文件总计386个整体大小15.21MB涵盖Java后端代码、Vue前端页面、XML映射文件、数据库脚本、可执行批处理脚本及说明文档结构划分清晰便于按需查阅。项目基于JDK1.8、SpringBoot、Mybatis、MySQL 5.7采用B/S架构与MVC设计模式实现药品管理、库存管理、销售管理等核心业务完整展现前后端分离系统的开发与部署流程。已有177人学习浏览源码均经过严格测试可直接导入常见开发工具运行并附有环境配置说明。博主提供使用答疑适合需要完整项目参考、快速完成毕设或深入理解SpringBoot实战的学习者。1. SpringBoot 药店管理系统一份能跑起来的 Java 项目源码这是一个 Java 药店管理系统压缩包里面不是单个 class 文件而是一套完整的 SpringBoot 后端加 Vue 前端 dist 资源。它解决的场景很具体药店每天需要维护药品分类、商品信息、库存数量和价格手工记账容易出错这套系统通过 B/S 结构让操作员在浏览器里完成管理后台用 MySQL 存数据。对于做毕业设计或课程设计的计算机专业学生这份代码的价值在于启动链路完整从 install 到 run 都有脚本说明对正在走 Java 学习路线的人它又是一份可以对照看的 SpringBoot Mybatis 项目。下面先以压缩包里的文件为入口把启动链路讲清楚。2. 源码包结构与启动链路三个 bat 脚本背后的 SpringBoot 约定2.1 压缩包文件清单与 Vue 打包产物先看压缩包根目录1-install.bat、2-run.bat、3-build.bat三个批处理文件app.bb5659c3.css、chunk-vendors.1f0a25b2.css、style.css是打包后的样式文件index.html是页面入口favicon.ico是浏览器标签页图标必读推荐.docx说明文档。要理解这套资源得先分清「前端打包产物」和「后端工程」两部分。app.bb5659c3.css和chunk-vendors.1f0a25b2.css这种带 hash 的文件名是 Vue 执行npm run build之后生成的chunk-vendors通常是第三方依赖合并包app是业务代码包index.html引用它们。SpringBoot 默认会把classpath:/static/下的文件映射到根路径所以把整个 dist 目录内容复制到src/main/resources/static/浏览器访问http://localhost:8080/就能直接看到页面。这种做法的好处是不需要单独部署 nginxTomcat 内嵌在 SpringBoot 进程里省去一道联调配置。再看 bat 文件。Windows 下直接双击顺序一般为先1-install.bat做初始化然后3-build.bat编译打包最后2-run.bat启动。因为文件命名是 1、2、3容易让人误以为执行顺序也是 1、2、3实际上从动作看3-build 应该夹在 install 和 run 之间。我用一个表格把它们的典型职责列出来脚本典型内容执行结果1-install.batmvn install、创建数据库、导入 SQL本地 Maven 依赖就绪数据库表结构存在3-build.batmvn clean package -DskipTeststarget 目录下生成可执行 jar2-run.batjava -jar target/xxxxx.jarSpringBoot 启动Tomcat 监听 8080需要注意install 放在第一位并不代表它只能执行一次。如果切换了数据库连接地址或修改了依赖重复执行 install 也不会出错只是会把已有 jar 重新安装到本地 Maven 仓库。真正每次改代码都要跑的是3-build.bat。2.2 环境对齐与环境变量配置这套系统的运行环境是 Windows 或 Mac但 bat 脚本只支持 Windows。Mac 用户直接把命令拆成三步即可。先检查版本是不是目标组合java -version mvn -v mysql --versionjava -version能看到1.8.0_xxx说明 JDK 8 已生效mvn -v显示 Maven 3.6 左右mysql 命令行能连上 5.7 实例。如果终端提示找不到命令优先检查 JAVA_HOME 和 MAVEN_HOME。实际部署时发现jdk 装好但命令行无效绝大多数是环境变量没配对或者 Path 里没有加%JAVA_HOME%\bin。这是 Java 基础里最不值得浪费时间的坑第一次配好后建议用echo %JAVA_HOME%确认。环境确认没问题后进入项目根目录执行初始化1-install.bat如果是 Mac 或 Linux在终端里执行等价命令mvn install -DskipTests mysql -uroot -p sql/init.sql-DskipTests跳过测试用例避免因为环境差异导致某些集成测试失败毕业设计阶段没有必要跑完整测试套件。如果 Maven 下载依赖很慢修改~/.m2/settings.xml把 central 镜像换成阿里云https://maven.aliyun.com/repository/public这一条对国内环境几乎是必做项。2.3 JDK 1.8、Maven 3.6、MySQL 5.7 的匹配关系这套工程为什么没有采用更新的 JDK 17 或 MySQL 8核心原因是依赖栈。SpringBoot 2.x 系列基于 JDK 8 编译内嵌 Tomcat 8/9Mybatis 的 starter 在这个组合下兼容性最好MySQL 5.7 的默认认证插件是mysql_native_password恰好是旧驱动支持的方式。如果把 MySQL 升到 8.0驱动类要从com.mysql.jdbc.Driver改成com.mysql.cj.jdbc.Driver连接串还必须加时区参数否则 SpringBoot 启动会直接报Server returns invalid timezone。这些一眼看不出来的问题会在后面排错章节单独说。一个典型application.yml里的数据源配置通常这样写server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/pharmacy?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.pharmacy.entitycharacterEncodingutf8是中文不乱码的关键useSSLfalse避免本机 MySQL 没有 SSL 证书时的握手警告serverTimezone只对 MySQL 8 必要5.7 下无所谓。mapper-locations指向 XML 文件type-aliases-package让 XML 里可以直接写Medicine而不是全限定类名。这两个配置会影响第三章的 Mapper 写法。3. SpringBoot 后端分层从 Controller 到 Mapper 的药品管理流程3.1 MVC 分层与请求走向这套药店系统的后端按 SpringBoot 最常见的三层结构组织Controller 负责接收 HTTP 请求和返回 JSONService 处理业务规则Mapper/DAO 负责和 MySQL 做数据交换。再加上 Mybatis 的 XML 映射文件SQL 可以脱离 Java 代码单独维护。这个结构在 Java 面试题里属于高频考点面试官通常会问「一次请求从浏览器到数据库经历了什么」。答案并不复杂浏览器通过 Ajax 请求访问 Controller 的 URLController 调 ServiceService 调 MapperMapper 执行 SQL 返回结果再原路返回 JSON。药品管理功能里最核心的实体是药品信息我通常把它命名为 Medicine。一张表的字段至少包含药品分类、编码、名称、规格、单位、库存、进价、售价、生产日期、有效期、状态。注意库存字段是业务重点过多的前端校验不可靠真正要控制库存的是 Service 层。比如保存订单扣库存时用更新的 SQL 而不是先查后改避免并发下超卖。3.2 药品新增接口的实现骨架不管压缩包里的包名是com.pharmacy还是com.medicine原理是同一套。下面是我会采用的新增药品骨架省略了部分校验逻辑RestController RequestMapping(/api/medicine) public class MedicineController { Autowired private MedicineService medicineService; PostMapping public ApiResponse save(RequestBody Medicine medicine) { medicineService.addMedicine(medicine); return ApiResponse.success(); } }Controller 负责把 HTTP 请求里的 JSON 绑定到 Medicine 对象。RequestBody表示请求体必须是 JSON 格式PostMapping接收 POST 方法。返回ApiResponse是统一响应体常见字段是code、message、data前端通过code 200判断成功或失败这样的好处是错误处理可以集中做。Service 层承接业务逻辑事务边界也在这里Service public class MedicineService { Autowired private MedicineMapper medicineMapper; Transactional public void addMedicine(Medicine medicine) { if (medicine.getStock() 0) { throw new RuntimeException(库存不能为负数); } medicine.setCreateTime(new Date()); medicineMapper.insert(medicine); } }Transactional保证插入过程中任何一个步骤失败都会回滚如果后续加入「新增药品同时记录操作日志」的逻辑事务会把药品表和日志表的写入包在一起。库存校验放在 Service 而不是 Controller是因为 API 层可以被多个接口调用基础规则下沉后才能统一生效。Mapper 接口和 XML 是 Mybatis 的核心Mapper public interface MedicineMapper { int insert(Medicine medicine); }XML 文件放在src/main/resources/mapper/MedicineMapper.xmlinsert idinsert useGeneratedKeystrue keyPropertyid insert into medicine (name, specification, unit, stock, sale_price, create_time) values (#{name}, #{specification}, #{unit}, #{stock}, #{salePrice}, #{createTime}) /insertuseGeneratedKeystrue配合keyPropertyid插入后数据库自增主键会自动回填到传入对象的 id 字段。#{}是预编译占位符能防止手工拼接 SQL 的注入问题千万不要用${}去拼接普通参数。价格字段在数据库里建议用decimal(10,2)Java 用BigDecimal接收避免 float 的精度丢失。3.3 Mybatis 动态 SQL 与库存更新的常见边界做药店系统不可能只做单表新增。药品列表页一般支持按名称模糊查询、按分类筛选、按库存预警过滤。Mybatis 的where和if是应对这类场景的标准写法select idselectPage resultTypeMedicine select id, name, specification, unit, stock, sale_price from medicine where if testkeyword ! null and keyword ! and name like concat(%, #{keyword}, %) /if if testcategoryId ! null and category_id #{categoryId} /if if testlowStock ! null and lowStock and stock lt; 20 /if /where order by id desc /selectwhere标签会自动去掉第一个条件前面的andif相当于动态拼接字符串。注意lt;是因为 XML 里必须转义很多新手在这个位置被坑过把stock 20写进 XML 会导致解析失败。库存更新建议使用 SQL 表达式而不是先查询再赋值update medicine set stock stock #{count} where id #{id} and stock #{count} 0这条 SQL 把判断条件和更新放在同一个语句里在高频操作下比「select 再 update」稳定得多。如果后续要支持多门店和更复杂的并发控制再考虑引入乐观锁版本号。4. Vue Ajax 页面联调静态资源挂载与接口对接4.1 SpringBoot 如何托管 Vue 打包资源这一章进入 B/S 架构的另一面。SpringBoot 对静态资源有默认规则classpath:/static/下的index.html就是访问/的页面浏览器加载index.html后会自动请求其中的/css/app.bb5659c3.css和/js/chunk-vendors.1f0a25b2.js等文件SpringBoot 会自动到/static/查找。因此拿到压缩包里的前端资源后正确操作是把这些文件全部放到src/main/resources/static/目录下再执行3-build.bat重新打包。直接双击index.html打开是看不到数据的因为这时的接口域名还是http://localhost:8080而文件协议下跨域请求会被浏览器直接拦截。这也解释了一个常见现象为什么压缩包里有两个index.html。一个可能是 Vue 项目根目录的源入口另一个是打包要用的 dist 入口。实际运行时以后者为准如果修改了前端代码重新 build只覆盖 dist 里的静态目录即可。4.2 Ajax 调用后端接口的典型写法前端页面不外乎做三件事加载药品列表、提交新增表单、完成入库出库按钮。下面的 Vue 实例展示了一个列表加载的常见写法new Vue({ el: #app, data: { medicines: [], keyword: }, mounted() { this.loadMedicines(); }, methods: { async loadMedicines() { const params new URLSearchParams(); params.append(page, 1); params.append(size, 10); params.append(keyword, this.keyword); const res await fetch(/api/medicine/list?${params.toString()}); const data await res.json(); if (data.code 200) { this.medicines data.data.records; } } } });mounted是 Vue 生命周期中页面渲染完成后执行的回调在这里发起请求能保证 DOM 已经准备好。fetch是浏览器原生接口不需要引入 axios适合毕业设计这种尽量少依赖的场景。后端返回格式如果包含code/data/records这种包一层分页结果的结构往往是因为引入了 PageHelper 做分页。data.data.records两层 data 会让新手困惑实际第一层是接口统一包裹第二层才是分页对象。这套系统里我常把接口收敛成下面表格里的三类前端照着这个路径对接就不会乱请求路径方法说明核心参数/api/medicine/listGET分页查询药品列表page、size、keyword、categoryId/api/medicinePOST新增药品JSON 格式 Medicine/api/medicine/stockPUT库存变更id、count、typetype通常用 0 表示出库1 表示入库。后端根据 type 决定是stock - count还是stock count这个参数在库存变更日志里也会记录下来。新增药品时需要注意请求头的 Content-Type用 fetch 时要显式写await fetch(/api/medicine, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ name: 阿莫西林胶囊, specification: 0.25g*24, stock: 100, salePrice: 12.50 }) });JSON.stringify把对象转成字符串SpringBoot 的RequestBody才能正确反序列化。如果漏掉Content-Type: application/json后端会把 body 按表单解析RequestBody会收到空对象或直接报 415 错误。4.3 联调时最容易出现的跨域问题本地开发时如果 Vue 跑在 8081 端口SpringBoot 跑在 8080 端口前端请求就会出现 CORS 跨域。对于这套代码如果最终是前后端一起打包到 SpringBoot根本不会触发跨域保持一个端口最省事。只有当你在 IDEA 里同时启动 Vue dev server 和 SpringBoot 时才需要处理。我一般推荐用代理方式解决修改 Vue 的vue.config.jsdevServer: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }/api开头的请求会被代理转发到 8080浏览器看到的是同源请求便不会拦截。如果是后端单独部署到线上也可以在后端统一加跨域配置但只适合开放测试接口生产环境尽量保持同源少开跨域。5. 启动后验证与排错从日志到接口测试的关键动作5.1 启动失败先看这三个症状运行2-run.bat如果启动失败第一个动作是看控制台最后 20 行而不是重新打包。最常见的三类错误Access denied for user rootlocalhost数据源账号密码和application.yml不一致。Port 8080 was already in use之前启动过的 Java 进程占用端口Windows 下用netstat -ano | findstr 8080看 PID再taskkill /F /PID pid。Table pharmacy.medicine doesnt exist说明1-install.bat没有成功建表手动执行 SQL 初始化。对于后两类日志里会有明确异常栈把关键行复制出来搜索比盲改配置高效。5.2 用 curl 验证接口是否可通启动成功后先不急着打开页面用 curl 把接口链路验证一遍。下面是一条新增药品的测试命令curl -X POST http://localhost:8080/api/medicine \ -H Content-Type: application/json \ -d {name:感冒灵颗粒,specification:10g*9袋,stock:50,salePrice:19.80}返回{code:200,...}说明后端链路正常。再访问curl http://localhost:8080/api/medicine/list?page1size10验证分页查询。如果新数据能查到再打开浏览器看页面这样能直接分清问题在前端还是后端。5.3 一次替换默认环境配置的小技巧拿到源码后通常要改三件事数据库密码、端口号、文件上传路径如果有。不要只改一处而忽略打包后的 jar。SpringBoot 支持启动参数覆盖配置java -jar target/pharmacy.jar \ --spring.datasource.password新密码 \ --server.port9000这种方式适合临时验证。需要长期生效时把配置写到外部application.yml放在 jar 同目录的config/下SpringBoot 会优先读取外部配置避免每次打包都改代码。SpringBoot 的配置优先级是config/目录下配置 jar 同目录配置 jar 包内部配置启动日志里加载了哪个文件会明确打出来再结合curl http://localhost:9000/api/medicine/list?page1size1的返回结果即可确认外部配置已经生效。本文还有配套的精品资源点击获取