新闻详情

新闻详情

首页 / 资讯中心 / 详情

SpringBoot医药管理系统实战:从表结构设计到库存批次与销售核心实现

发布时间:2026/9/29 16:53:27来源:尧图网络
SpringBoot医药管理系统实战:从表结构设计到库存批次与销售核心实现
大四那会儿我接过不少基于SpringBoot的毕业设计代做需求医药管理系统是每年都会遇到的老面孔。一开始我以为这只是个普通的CRUD项目真正动手后才发现医药管理系统跟学生信息管理、商城系统完全不是一个量级——它涉及到库存批次、药品有效期、供应商往来、销售单拆分这类真实业务逻辑稍不留神表结构设计就会翻车。如果你正准备用SpringBoot做医药管理系统或者已经在写了但总觉得哪里不对劲这篇文章就是我踩过坑之后整理出来的完整复盘从需求拆解、表结构设计到核心代码实现再到部署阶段的坑一整套都摊开讲。1. 项目整体设计与需求拆解1.1 医药管理系统到底要管什么在动手写代码之前先搞清楚业务边界。很多同学一上来就建了user表和drug表写两个增删改查就以为完事了结果答辩的时候老师问一句“药品过期了怎么处理”“库存不足怎么预警”就直接卡壳。一个合格的医药管理系统核心业务至少覆盖六块药品信息管理药品通用名、商品名、规格、生产厂家、批准文号、零售价、库存管理入库、出库、盘点和有效期跟踪、供应商管理供应商档案和往来记录、销售管理收银开单、销售明细、退货、处方管理医院处方录入和审核以及系统用户管理管理员、药师、收银员/店员的分角色权限。用一张图概括业务闭环就是“采购入库 → 库存沉淀 → 销售出库 → 库存扣减”一切功能都绕着这个闭环转。理解了业务范围后你会发现库存和销售才是系统的核心药品信息和用户管理都是前置基础。做设计时优先把库存流水和销售单据的模型想清楚再回头补基础档案整个项目就会顺很多。1.2 为什么选SpringBoot而不是SSH或其他框架很多教程讲SpringBoot都会扯“自动配置”“约定优于配置”这些概念其实用大白话讲就一点SpringBoot把SSH时代那堆繁琐的XML配置全帮你干了你只需要写上依赖、写好application.yml就能直接跑起来。具体到医药管理系统这个场景选择SpringBoot有三个直接原因开发效率高只需要一个可执行的JAR包就能部署本地开发和服务器部署成本都低对课程设计和毕业设计来说太合适了。生态成熟SpringBoot整合MyBatis-Plus、Spring Security、Redis、Druid这些常用组件都是现成的starter几乎不需要额外配置可以把精力集中在业务代码上。社区资料多讲实话遇到问题搜索一下基本都能找到答案。这在时间紧张的毕设周期里是非常重要的隐性优势。至于SpringBoot 2.x还是3.x我个人的建议是选2.7.x版本。原因很现实3.x要求JDK 17及以上不少学校机房和服务器还停留在JDK 8而且很多第三方starter对3.x的适配还有兼容性问题。用2.7.x能省掉大量环境上的不可控因素。1.3 技术栈选型服务端渲染还是前后端分离医药管理系统常见的做法有两种一种是使用SpringBoot Thymeleaf页面和后端放在一个工程里适合人员不多、版本简单的小药房场景另一种是SpringBoot Vue前后端分离更适合需要小程序端或者多端适配的稍大场景。这两种方案我都实测过给你一个选型参考对比维度SpringBoot ThymeleafSpringBoot Vue前后端分离开发上手难度低会HTML就能改页面中等需要懂前端工程化和接口封装部署形式单JAR包直接扔服务器跑前端静态文件加后端接口服务需要Nginx转发接口文档直接看Controller就行需要维护API文档联调成本高毕设答辩表现演示直观但显得“传统”展示效果好技术点丰富适合人群时间紧、想跑通主流程想冲刺优秀论文、展示全栈能力如果你是应届生做毕设我建议优先选SpringBoot Thymeleaf稳妥如果你已经掌握了Vue基础那就上前后端分离答辩时长脸。不管选哪个后端核心代码的写法几乎是一致的所以这篇博文重点讲后端的实现思路你完全可以从这里复用过去。2. 数据库设计整个系统的地基2.1 数据库设计思路从业务单据反推表我见过太多人一上来就画ER图画出来的表之间关联混乱表名和字段名也不规范。正确的姿势应该是从业务单据出发反推表结构。什么意思呢比如“销售”这个动作在现实中对应一张销售小票主表和多个药品条目明细表。所以数据库里就应该有sale_order和sale_order_item两张表前者记录订单号、收银员、总金额、销售时间后者记录每一行药品的信息药品ID、单价、数量、小计金额。药品入库也是同样的逻辑一张stock_in入库单对应多条明细stock_in_item。只有业务单据拆清楚了表结构才会自然浮现而不是凭空想“我要建一张表存订单”。这个思路对医药管理系统来说特别重要因为库存变动和财务流水都是靠单据来追溯的表关系建歪了后面查账、统计报表全都对不上。2.2 五张核心表的字段设计与关系基于上面的思路医药管理系统里最核心的表可以精简成这几张角色表、菜单表这些就不展开了用户表sys_user字段名类型说明user_idbigint主键usernamevarchar(50)登录名passwordvarchar(100)BCrypt加密存储real_namevarchar(50)真实姓名role_idbigint关联角色statustinyint0停用1启用药品表drug字段名类型说明drug_idbigint主键drug_codevarchar(50)药品编码追踪的唯一标识generic_namevarchar(100)通用名trade_namevarchar(100)商品名specvarchar(50)规格如“0.25g*24片”manufacturervarchar(100)生产厂家approval_numbervarchar(50)批准文号retail_pricedecimal(10,2)零售价stock_quantityint总库存量warning_lineint库存预警线库存批次表drug_batch这是整个设计里最关键的表后面单讲。供应商表supplier字段比较常规包含供应商名称、联系人、电话、地址。销售主表sale_order 销售明细表sale_order_item主表有订单号、总金额、支付方式、收银员ID明细表有药品ID、数量、单价、小计。2.3 药品批次与有效期跟踪的坑药品和普通商品最大的差别就是有效期。一个药店的真实场景是同一种药品可能分好几批进货每批的批号不同、有效期不同。如果只在drug表里放一个库存数字压根没法处理“先过期先出”的原则。所以必须单独建一张批次表drug_batch字段名类型说明batch_idbigint主键drug_idbigint关联药品batch_novarchar(50)进货批次号quantityint本批次库存量production_datedate生产日期expire_datedate有效期至supplier_idbigint来自哪个供应商药品总库存drug.stock_quantity是drug_batch中各批次数量之和。这样做的价值在于可以用SQL按expire_date排序在销售时优先扣减最先到期的批次这就是医药行业里说的“先进先出”。另外建议写一个定时任务每天扫一遍drug_batch表把有效期在30天内或已经过期的批次提取出来生成预警列表。虽然对毕设来说定时任务不是必须的但有了这个设计答辩时能解释得很出彩。3. 核心功能实现与实操细节3.1 基于JWT的登录认证与权限控制登录模块是几乎所有系统的门面。SpringBoot可以做Session登录也可以做JWT登录。医药管理系统我推荐用JWT理由是无状态、跨端友好——以后如果接个小程序同一套认证逻辑也能复用。依赖配置很简单dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.11.5/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-impl/artifactId version0.11.5/version scoperuntime/scope /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-jackson/artifactId version0.11.5/version scoperuntime/scope /dependency密码存库时用BCryptPasswordEncoder加密不要明文存储。登录成功后签发TokenToken里只放userId和roleId这种必要信息不要放敏感数据。JWT生成的工具类核心逻辑大致是public String generateToken(Long userId, String role) { return Jwts.builder() .setSubject(String.valueOf(userId)) .claim(role, role) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() 1000 * 60 * 60 * 24)) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); }然后写一个JwtInterceptor实现HandlerInterceptor放行登录接口其他接口都从请求头里取Token并校验。拦截器注册到WebMvcConfigurer里时注意排除静态资源和登录接口否则会误伤Thymeleaf的静态文件。一个小坑很多项目第一次启动时会报Failed to parse JWT之类的错仔细查一下是不是jjwt版本和JDK版本不兼容或者SECRET_KEY长度不够建议至少32个字节。3.2 药品管理模块分页查询与模糊搜索药品管理模块看起来是个普通CRUD但有几个细节值得注意。查询接口一定支持条件分页条件包括药品名称模糊、厂家模糊、库存预警状态等。用MyBatis-Plus实现分页查询很顺手。先配置分页插件Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }查询代码public PageDrug pageDrugs(int page, int size, String keyword) { LambdaQueryWrapperDrug wrapper new LambdaQueryWrapper(); if (StringUtils.hasText(keyword)) { wrapper.like(Drug::getGenericName, keyword) .or().like(Drug::getTradeName, keyword) .or().like(Drug::getDrugCode, keyword); } wrapper.orderByDesc(Drug::getDrugId); return drugMapper.selectPage(new Page(page, size), wrapper); }这种带条件的LambdaQueryWrapper写法既避免了SQL注入风险又比字符串拼接清晰得多是我最常用的写法。3.3 库存管理入库、出库与盘点库存模块是医药管理系统里业务逻辑最重的部分核心难点在于库存扣减的一致性。先说入库。入库时要做两件事往drug_batch表插入新批次记录同时累加drug表的总库存。这两步必须放在同一个事务里否则会出现批次表加了记录但总库存没变的脏数据。入库代码大致是Transactional(rollbackFor Exception.class) public void stockIn(StockInDTO dto) { DrugBatch batch new DrugBatch(dto); drugBatchMapper.insert(batch); drugMapper.increaseStock(dto.getDrugId(), dto.getQuantity()); }increaseStock写成SQL更新UPDATE drug SET stock_quantity stock_quantity #{quantity} WHERE drug_id #{drugId}这里用“先查再改”有个隐藏问题并发情况下可能丢更新。用SQL原地自增就好很多。这是很容易被忽略的细节但在真实业务里特别重要。再说出库销售扣减库存。卖药时只要在drug表减总库存就行但如果要做到先进先出需要按批次扣减。代码逻辑是查询该药品未过期的批次按expire_date升序。从最早批次开始扣减每批次扣到0就移到下一批次。累计扣减数量等于销售数量写入销售明细。这一步千万记得加Transactional高并发场景下还可以用乐观锁在drug_batch加version字段做并发保护。库存盘点就更简单了后台发起盘点单记录系统库存和实盘库存生成差异明细实际项目中盘点单可以独立建表。毕设里把盘点做成一个“一键生成差异报表”的接口就足够覆盖需求了。3.4 销售单与营业额统计销售单模块要处理的不仅是“卖药”还包括撤销销售。撤销销售时要把库存加回去——不是简单加回总库存而是要按原销售明细重新生成一个“入库批次”或者单独记录退货流水。很多毕设在这里直接忽略答辩时就露怯了。统计模块是这类系统最容易展示功能亮点的部分。比如查“本月每天营业额”写一条SQL就搞定SELECT DATE(order_time) AS day, SUM(total_amount) AS amount FROM sale_order WHERE order_time #{start} AND order_time #{end} GROUP BY DATE(order_time) ORDER BY day再配合JFreeChart或ECharts画个折线图、柱状图展示月度销售趋势给人一种“系统很完整”的感觉。技术实现不复杂但演示效果极佳。4. 常见问题与排查技巧实录4.1 本地开发环境搭建的完整流程如果你第一次创建SpringBoot工程我建议直接去Spring Initializr生成基础项目手动挑依赖十有八九会漏这漏那。在start.spring.io页面行业务场景时需要勾选的依赖长这样Spring Web提供HTTP接口能力必选。Spring Data JPA 或 MyBatis持久层框架。医药管理系统我选MyBatis-Plus先在pom里手动加MyBatis-Plus的依赖比JPA更直观。MySQL Driver连数据库用。Validation参数校验比如药品价格不能为负。Lombok省掉getter/setter代码清爽。生成后修改application.yml最基础的内容包括数据源配置、MyBatis-Plus配置和日志级别spring: datasource: url: jdbc:mysql://localhost:3306/medicine_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl前端用Thymeleaf的话配置视图解析器spring: thymeleaf: prefix: classpath:/templates/ suffix: .html cache: falsecache: false尤其重要不然你改了HTML页面刷新后还是旧页面会误以为是浏览器缓存问题调试半天发现被Thymeleaf缓存坑了。4.2 高频报错时区、SQL模式、静态资源404我在多个医药管理系统项目上实测下来下面这三个问题几乎每个项目都会遇到一次。第一个时区问题报错形如CST时间错乱或者连接MySQL时提示Could not create connection to database server。解决方案是在连接串里明确指定serverTimezoneAsia/Shanghai同时MySQL端也要保持一致。如果还不行查看一下MySQL全局时区配置用set global time_zone 8:00临时解决。第二个MySQL 5.7的SQL Mode5.7版本默认开启了ONLY_FULL_GROUP_BY如果你写分组统计SQL时select了没被group by的列会直接报错。排查SQL时报500别慌先去查后台日志错误信息里会明确告诉你违反了什么约束。开发环境可以直接关掉这个模式SET GLOBAL sql_mode STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION;但注意这只适合本地开发生产环境建议老老实实改SQL。第三个静态资源404Thymeleaf页面引了CSS、JS但资源加载不出来。检查一下你是否把静态文件放到了src/main/resources/static目录下。SpringBoot默认只从这个路径提供静态资源templates目录只放模板页不放CSS很多人一开始搞反了。4.3 事务与并发层面容易踩的坑医药系统有真实金额和库存事务是底线。只要涉及多表更新的操作入库、出库、销售单创建都要显式加上Transactional(rollbackFor Exception.class)。在事务方面新手最容易犯的三类错误事务没生效在同一个类里A方法调B方法B上有Transactional但B的事务不生效。因为Spring的事务是基于AOP代理的内部调用不会走代理。解决办法是拆成不同的Bean或者自己注入本类代理。捕获了异常没抛出女朋友让我改一个bug我改了三天的经典案例。方法里try-catch了异常但没有抛出事务一看“没异常啊”就正常提交了数据就错了。要么别在事务方法里catch要么catch后手动TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()。接口幂等性用户连续点了两次提交按钮结果生成两笔销售单。前端可以做按钮防重复点击后端也可以在sale_order表加一个唯一索引比如订单号从根上拦截。4.4 项目结构改造与扩展经验如果你现在拿到的医药管理项目结构比较乱比如所有包都写在顶层没有分层我的建议是至少按controller、service、mapper、entity、dto、util这几层去梳理。分层不是形式主义是为了出问题的时候不用翻遍全工程找一行代码。后续如果要扩展功能我梳理了几个方向供参考接入Redis缓存把高频查询的药品信息缓存起来减少数据库压力顺便在简历上写一句“引入Redis缓存热点数据”。对接短信/邮件通知库存低于预警线时发通知给采购员属于锦上添花的亮点功能。集成接口文档SpringDoc自动生成接口文档方便答辩时给老师看你的API设计。扩展时要控制范围核心功能保证稳定亮点功能点到为止别把毕设做成大杂烩。我个人在实际操作中的体会是医药管理系统这类“中规中矩”的题目恰恰是最考验基本功的。业务边界清晰、需求明确你能把库存批次、事务一致性、权限认证这些点讲透答辩效果不比那些花里胡哨的选题差。最后再分享一个小技巧写代码之前先把所有接口的URL和参数用表格列出来列到30个接口以上开工基本就不会遇到“写一半发现表结构不够用”这种返工。这个习惯我后来做商业项目也一直在用非常稳。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

继续教育学生降AI率实操指南:九大工具与组合策略 2026/9/29 17:47:36

继续教育学生降AI率实操指南:九大工具与组合策略

1. 为什么继续教育学生的AI痕迹格外扎眼先说个扎心的事实:现在高校和继续教育学院用来查AI率的系统,早就不是当年那套只比对数据库重复率的玩意儿了。像Turnitin的AI检测、知网的AIGC检测、以及各种基于Perplexity(困惑度)算法的检…

阅读更多 →
计及N-k安全约束的含光热电站电力系统优化调度Matlab实现 2026/9/29 17:47:30

计及N-k安全约束的含光热电站电力系统优化调度Matlab实现

做电力系统调度的朋友应该都有体会:论文里把模型写清楚是一回事,真要在Matlab里把代码跑起来、把结果调合理,完全是另一回事。尤其带安全约束的优化调度,约束一多,矩阵动不动就几百上千行,稍不注意就出现“…

阅读更多 →
Redis五大数据类型实战详解:原理、场景选型与排障 2026/9/29 17:47:30

Redis五大数据类型实战详解:原理、场景选型与排障

1. 为什么先把数据类型想清楚,比背命令更重要做后端开发的,几乎没有人能绕开 Redis。缓存、分布式锁、排行榜、消息队列、计数统计,样样都有它的身影。但我在面试和带团队时发现一个很有意思的现象:很多人提到 Redis,能…

阅读更多 →
Java后端门诊服务聚合系统实战:Spring Boot模块化设计与订单状态管理 2026/9/29 17:47:30

Java后端门诊服务聚合系统实战:Spring Boot模块化设计与订单状态管理

简介:基于Java语言开发的门诊服务聚合系统设计源码,面向医疗信息化开发者和有一定Java基础的后端学习者,可用于解决预约挂号、排队叫号、医疗记录管理等门诊服务场景中的流程聚合、数据管理与系统整合问题。整个资源包共包含51个文件&#xf…

阅读更多 →
FastDFS高可用集群部署实战:从双Tracker到双Storage的完整指南 2026/9/29 17:47:30

FastDFS高可用集群部署实战:从双Tracker到双Storage的完整指南

文件存储这种基础设施,平时不起眼,一到容量满了或者节点宕了就容易鸡飞狗跳。我前后用FastDFS搭过好几次文件集群,从单机玩具到带实际流量的生产环境,一点一点把坑踩平,这篇笔记就是围绕FastDFS高可用集群部署整理的。…

阅读更多 →
编译好的Crashpad库(x86/x64, Release/Debug)接入指南与避坑 2026/9/29 17:47:29

编译好的Crashpad库(x86/x64, Release/Debug)接入指南与避坑

简介:本资源为已编译完成的Crashpad崩溃报告库,面向Windows平台下的软件开发人员、系统工程师与QA团队,用于在应用程序中集成崩溃捕获与上报能力,帮助快速定位并修复线上或测试环境中的崩溃问题。包内按架构与构建模式划分为relea…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉