
又到毕业设计选题的季节在Java方向里“药品管理系统”绝对是出现频率最高的几个题目之一。很多同学看到SpringBoot加这个题目第一反应就是“不就是增删改查吗”结果答辩时被老师追问一句“什么叫全流程管理”就卡住了。我这两年帮学弟学妹们看过不少这种项目也亲手带过几个完整从零搭建的药品管理系统。说实话这题目能做得很浅也能做得很有层次差别就在于你的系统是不是真的把药品从“进”到“出”到“查”的整条链路打通了。这篇内容把我拆解和落地这个题目的完整过程写下来不打算只给一个“能跑”的代码堆砌而是把设计思路、表结构、状态流转、权限控制、报表统计这些容易被忽略但恰恰是答辩加分项的部分全部摊开讲清楚。如果你正在做毕业设计或者刚入行想拿一个SpringBoot项目练手这篇可以直接当一份带避坑说明的施工图纸来用。1. 整体设计与思路拆解1.1 为什么药品管理系统能成为毕设热门先说一个很现实的问题每年那么多同学选这个题目教务处老师也爱出这个题目不是因为药品管理系统真的有多复杂而是因为它几乎包含了一个信息管理系统的所有标准要素。药品本身有非常完整的生命周期管理需求——药品有生产厂家、批准文号、批次、有效期、库存数量、供应商来源、销售去向天然就能牵出多张表之间的关联关系。而且医药流通场景下的业务规则非常具体有效期临近要预警、库存低于下限要补货、药品入库要有供应商和入库单、销售出库要联动库存扣减、管理人员需要看到每个维度的统计报表。用生活场景来类比就是如果做一个“笔记本管理系统”你大概率只有商品、分类、库存三张表业务逻辑几乎为零。但药品管理系统不一样它逼着你把“单据、库存、有效期、角色权限、报表”这些独立概念串起来这不是纯粹表层的界面展示而是有真实业务深度的。对毕设来说这恰恰是老师愿意看到的“训练完整度”。再说得直白一点药品管理系统并不难做但它能让你在有限时间内把SpringBoot、MyBatis Plus、拦截器、定时任务、Vue或Thymeleaf这些技术全部过一遍知识点在同一个项目里互相咬合。这是很多其他题目做不到的。1.2 全流程到底包含哪些模块部分同学对“全流程”这个词没有概念觉得把药品信息能增删改查就是全流程了。这种理解会让项目在结构上天然缺层。我拆这个题目时一般会把“全流程”拆成三条主线第一条是药品信息主线药品档案的建立、分类维护、厂家与供应商的关联、药品字典数据的管理。这是系统的地基所有单据最终都会关联到这条主线的记录上。第二条是库存流转主线入库单产生入库流水、库存数量增加、库存批次与有效期绑定销售或领用产生出库流水、库存数量扣减库存数量低于安全阈值触发预警效期临近触发过期提醒。这一条是最能体现“流程”的线也是答辩时最值得展开讲的部分。第三条是权限与报表主线系统里有管理员、库管员、收银/前台等角色不同角色能看到的菜单和操作按钮不同操作记录留痕最终按日、月、供应商、药品分类维度汇总数据和图表。模块划分上我建议至少包含以下这些功能域功能域核心功能典型角色登录与权限登录认证、菜单控制、操作权限所有角色药品档案管理药品基本信息、分类、厂家、批准文号管理员、库管员入库管理供应商选择、入库单生成、批次登记库管员出库/销售管理出库单、销售记录、批次扣减收银员、库管员库存管理库存查询、上下限预警、效期预警库管员、管理员供应商管理供应商档案、往来记录管理员统计报表进销存统计、月度销售、库存排行管理员操作日志关键操作记录管理员看到这些模块划分你就应该有一个意识系统的核心价值不在页面多漂亮而在“一条药品记录从采购计划变成销售记录之后你在任何一张表上都能把链路追溯出来”。所以后面的数据库设计、表关系、状态流转全部都围绕这条链路来做。2. 核心技术选型解析2.1 为什么用SpringBoot而不是传统SSH很多同学的疑问是学校课程可能教的是Servlet、JSP或者SSH为什么一出校门做项目都变成SpringBoot了。这个选择题本质上是在问“你愿意花多少时间在环境搭建和繁琐配置上”。传统SSH项目要配置一堆XML文件光数据源、事务、Spring和Hibernate的整合就能让新手耗掉两三天。SpringBoot的核心逻辑是“约定大于配置”你用默认配置就能跑起来需要自定义的地方再用配置类和注解覆盖。这种设计大大降低了起步成本让你把精力放在业务实现上。选SpringBoot还有一个很实际的答辩理由SpringBoot内嵌Tomcat打包成Jar就能直接运行这本身就展示了你对现代Java开发方式的理解。老师在演示环节看到你执行一条java -jar就能启动整个系统和看到你在IDE里点半天配置Tomcat印象是完全不同的。版本选择上我给一个目前比较稳的组合如果你用JDK 8选SpringBoot 2.7.x版本如果你用JDK 17可以直接上SpringBoot 3.x。但注意SpringBoot 3.x底层是Jakarta命名空间网上很多老代码是javax.*复制过来会直接编译报错这个坑我后面排查部分还会专门讲。2.2 持久层选型MyBatis Plus还是JPA持久层现在基本是两派Spring Data JPA和MyBatis Plus。对这个业务场景我会明确推荐MyBatis Plus理由非常实际。药品管理系统天然需要大量列表查询、分页、按条件筛选例如“查所有库存低于下限的药品”“按分类模糊查药品名称”“按时间查入库记录”。MyBatis Plus的QueryWrapper能把这类条件查询写得很清爽不用自己拼SQL字符串更重要的是它提供了实用的分页插件你传入页码和页大小就能直接拿到分页结果这在JPA里虽然也能做但两者写代码的直观程度差别很大。不过我也要说句公道话MyBatis Plus简化的是单表操作一旦涉及多表关联统计你依然要写XML里的复杂SQL。但系统里的统计SQL本来就需要精细控制这不叫缺点反而给了你在答辩时展示SQL功底的机会。如果题目的原始要求是基于实体自动生成建表语句那MyBatis Plus也有一些辅助工具类可以做这件事你可以用TableInfoHelper读取实体字段生成表结构但这种功能在毕设里不是必须的更稳的做法是直接用Navicat或MySQL命令行建表后面我会给出表结构SQL。2.3 前端方案Vue还是Thymeleaf前端选型要看你毕业设计任务书怎么写的。如果任务书写明了“前后端分离”那就用Vue加Axios如果只要求能运行、能演示我的建议是别给自己加太多工作量用Thymeleaf引擎渲染页面配合AdminLTE或Layui这类后台模板几天就能把管理端界面搭出来。但如果你打算用Vue有一个热词里的关键问题很多人会踩坑Vue项目如何打包放进SpringBoot中。实际流程是前端写完后执行npm run build生成dist目录然后把dist里的静态文件复制到SpringBoot的src/main/resources/static下。由于前后端分离时访问路径不同你还需要处理路由History模式的问题——最常见方案是把Vue的publicPath配置成/后端把非API路径转发到前端入口或者干脆用Hash路由省掉转发配置。对毕设来说用Hash路由是最省心的地址栏带上#/并不影响演示效果。2.4 权限认证Session、JWT还是Spring Security权限这块是很多同学实际做的时候最纠结的。用Spring Security吧配置流程复杂学习成本高完全不做吧连个登录拦截都没有答辩时一定会被问漏洞。我给一个适合毕设的中间方案使用JWT加SpringBoot拦截器。用户登录成功后后端生成一个带过期时间的Token返回给前端前端在每次请求的请求头里带上这个Token后端写一个拦截器统一校验校验通过就把用户信息放到ThreadLocal里供当前请求使用。为什么不用Session呢不是Session不能做而是JWT方案现在的工程实践更普遍你用这个方案答辩时容易被理解也能顺带解释“无状态认证”的概念。为什么不用完整Spring Security呢因为安全框架里的过滤器链、决策管理器这些概念对毕设项目来说属于过度设计而且你在配置上栽跟头的概率远大于它能给你带来的加分。3. 核心细节解析与实操要点3.1 数据库设计先解决表和字段一切才有根基数据库什么时候动工很多人上来就写代码写到哪想到哪这是项目后期返工的第一祸根。我通常的做法是先把核心表和关系画清楚再动代码。下面给一个简化但完整的核心表设计。用户表sys_user字段名类型说明idbigint主键usernamevarchar(50)登录名passwordvarchar(100)加密后的密码real_namevarchar(50)真实姓名role_idbigint角色ID角色表sys_role字段就是id、角色名、角色编码、备注。不需要做太细的权限表毕设阶段用“角色编码”控制菜单即可例如管理员角色编码为ADMIN在菜单渲染时判断。药品表drug_info这是系统最重要的表字段一定要完整。CREATE TABLE drug_info ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, drug_code VARCHAR(50) NOT NULL COMMENT 药品编码/批准文号, drug_name VARCHAR(100) NOT NULL COMMENT 药品名称, category_id BIGINT COMMENT 分类ID, manufacturer VARCHAR(100) COMMENT 生产厂家, specification VARCHAR(100) COMMENT 规格如10mg*20片, unit VARCHAR(20) COMMENT 单位如盒/瓶, purchase_price DECIMAL(10,2) COMMENT 采购价, sale_price DECIMAL(10,2) COMMENT 销售价, stock_quantity INT COMMENT 当前库存, min_quantity INT COMMENT 库存下限, valid_days INT COMMENT 默认有效天数, status TINYINT COMMENT 状态1正常 0停用, create_time DATETIME COMMENT 创建时间, update_time DATETIME COMMENT 更新时间 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT药品信息表;为什么要单独把min_quantity库存下限和valid_days有效天数拎出来因为它们是后面预警功能的数据依据。库存小于min_quantity就触发补货提醒根据入库时间和valid_days就能算出剩余效期再结合一个预警阈值字段就能实现效期提醒。入库单表stock_in和入库明细表stock_in_item入库单记录供应商、入库时间、操作人、总金额入库明细记录每一行药品、批次号、生产日期、有效期、入库数量、单价。这种“主单明细分表”的结构是所有单据类业务的通用设计你在答辩时要主动说明为什么拆成两张表——因为一张入库单可能包含十几种药品把药品信息重复塞进入库单会让表结构臃肿且无法分组统计。出库/销售表stock_out和出库明细表stock_out_item结构和入库侧对称同时增加一个业务类型字段区分是销售出库还是科室领用。供应商表supplierid、供应商名称、联系人、联系电话、地址。入库单通过供应商ID关联到供应商表。这些表的关系一句话总结就是药品表是主线主档出入库主单和明细通过“单号”串联明细里的批次信息关联到库存批次表所有的库存明细变动最终汇总成药品表的stock_quantity。3.2 药品状态与库存流转的状态机设计状态机这词听起来很高级其实放到业务里很直白一条药品记录在不同条件触发下从一种状态转变为另一种状态。药品关键状态有四个正常、低库存预警、到期预警、停用。状态流转逻辑是正常到低库存预警当药品库存量小于等于min_quantity时系统自动把状态置为低库存预警同时在库存预警列表里展示补货建议。正常或低库存到到期预警剩余有效期天数小于等于预警阈值比如30天状态置为到期预警。此时盘点或销售时需要优先出库这批药这个“先进先出”原则可以在出库选择批次时用排序规则实现。售出/出库后数量扣减出库成功后对应批次的库存数量减少药品表总库存同步减少。如果扣减后库存仍然大于下限则状态恢复正常。为了防止状态判断的逻辑散落在各种代码里我建议在Service层单独写一个updateDrugStatus(drugId)方法每次出入库成功后调用一次。这个方法里就是一组if判断根据当前库存数量、效期信息设置状态。不要小看这个封装它能避免你在多个地方重复写同样的判断逻辑也是“什么才叫工程化”的一个体现。3.3 库存预警与过期提醒的实现路径预警和提醒是该系统最值得展开的功能因为普通的增删改查体现不出定时任务的价值。在SpringBoot里实现定时任务非常轻量在启动类上加EnableScheduling然后在需要定时执行的方法上加Scheduled注解即可。一个典型的每日效期检查代码如下Component public class DrugStockTask { Autowired private DrugInfoMapper drugInfoMapper; // 每天凌晨1点执行 Scheduled(cron 0 0 1 * * ?) public void checkExpiredDrugs() { // 查出所有有效期内但剩余天数小于预警天数的药品 // 生成预警记录并可发送站内消息通知管理员 } }这里有一个容易忽略的问题定时任务在生产环境要考虑“防止多个实例重复执行”但毕设项目只部署一个实例所以不需要引入分布式锁。不过你答辩时如果能主动提一句“如果以后扩展成多实例部署这里需要引入分布式任务调度避免重复执行”这就是一个实实在在的加分表达。3.4 权限控制与操作留痕权限控制在页面上的表现是“不同角色登录看到不同菜单按钮点击权限不同”。实现方式可以很轻量数据库里建角色表用户表关联角色后端提供一个查询当前用户菜单的接口前端根据返回的角色编码渲染菜单后端拦截器对写操作接口做角色校验。比如入库单接口只允许库管员和管理员访问在拦截器或方法注解上判断即可。另外需要把“谁在什么时间做了什么”记录下来。最简单的方式是建一张sys_oper_log表通过AOP切面或者在关键Service方法里手动记录。不要觉得这是额外工作操作日志往往是答辩时老师喜欢翻的地方因为它是检验系统完整度的一个很好切入点。你做一个删除药品档案的操作然后去日志列表里看到一条完整记录这个体验比口头强调“我做了权限设计”可信得多。4. 实操过程与核心环节实现4.1 项目脚手架搭建与依赖配置创建项目有两种方式IDEA里直接选Spring Initializr或者去Spring官网生成骨架。如果你用的是IDEA注意Spring Initializr默认生成的版本可能偏新要结合本地JDK版本选择。以SpringBoot 2.7.18加JDK 8为例核心pom.xml依赖如下parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt/artifactId version0.9.1/version /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies为什么这么选版本MyBatis Plus 3.5.3和SpringBoot 2.7兼容良好分页插件和代码生成器都是可用的状态。JWT用jjwt这个库足够不需要引入更大的安全框架。Lombok是为了减少实体类里的getter和setter代码能省掉大量视觉噪音。目录结构建议按功能分包而不是按技术层分包“controller、service、mapper、entity、dto、config、common”。很多新手习惯先建一堆包名与技术层对应但实际项目里按业务模块聚合更清晰例如drug包下放药品相关的controller、service、mapper接口stock包下放出入库相关代码。4.2 登录认证与请求拦截实现登录验证码这里我就不多讲了先把整个流程跑通再考虑加Kaptcha生成验证码。核心登录逻辑是前端把用户名、密码传给后端后端查到用户后用BCrypt做密码校验校验成功生成JWT返回给前端。JWT生成的典型代码String token Jwts.builder() .setSubject(user.getUsername()) .claim(role, user.getRoleCode()) .setExpiration(new Date(System.currentTimeMillis() 1000 * 60 * 60 * 24)) .signWith(SignatureAlgorithm.HS256, 你的签名密钥) .compact();拿到Token后在后端写一个AuthInterceptor在preHandle里从请求头Authorization中取出Token解析成功就放行失败则返回401状态码。一个值得注意的技术点是拦截器只拦截需要鉴权的API登录接口本身要放行。为了避免把接口路径写死建议约定一个规则比如/api/auth/login放行其余/api/**需要验证Token。这种“白名单加拦截”的思路清晰且容易在答辩时解释。4.3 药品档案模块的分页查询与条件筛选药品档案模块是最标准的三层结构Controller接收参数Service写业务逻辑Mapper操作数据库。我用MyBatis Plus写一个分页加条件查询的示例这是几乎所有后端功能页的地基GetMapping(/page) public Result page(RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, RequestParam(required false) String drugName, RequestParam(required false) Long categoryId) { PageDrugInfo page new Page(pageNum, pageSize); LambdaQueryWrapperDrugInfo wrapper new LambdaQueryWrapper(); wrapper.like(StringUtils.hasText(drugName), DrugInfo::getDrugName, drugName) .eq(categoryId ! null, DrugInfo::getCategoryId, categoryId) .orderByDesc(DrugInfo::getCreateTime); PageDrugInfo result drugInfoService.page(page, wrapper); return Result.success(result); }注意这里用到了LambdaQueryWrapper而不是QueryWrapper好处是直接引用方法名而不是字符串字段名IDE能检查拼写错误改表结构时不容易留下隐藏Bug。还有一个很多人忽略的细节不要直接把DrugInfo实体返回给前端。如果实体里有采购价这种敏感字段还好关键问题是实体字段和页面展示字段往往不是一一对应的。正确做法是定义DTO对象例如DrugInfoVO只包含前端需要的字段同时在VO里加上分类名称、剩余天数这些计算字段。这不仅让接口更规范也能在答辩时说明你理解了实际开发中“实体、DTO、VO分离”的意义。4.4 报表统计模块的SQL实现统计报表是大部分毕设系统最薄弱的地方但也是最好展示思考深度的部分。不要用前端拿到全部数据后再在JS里算总和这种做法在数据量大的时候没有扩展性而且难以说服老师。正确做法是用SQL聚合后端只把汇总结果返回给前端。以月度入库金额统计为例核心SQLSELECT DATE_FORMAT(create_time, %Y-%m) AS month, COUNT(*) AS orderCount, SUM(total_amount) AS totalAmount FROM stock_in WHERE create_time 2024-01-01 AND create_time 2025-01-01 GROUP BY DATE_FORMAT(create_time, %Y-%m) ORDER BY month;对应的Mapper方法用Select注解或XML都行。前端统计页用ECharts展示柱状图、折线图图表数据直接从后端接口拿。这一整套实现思路可以扩展开来月度出库排行、供应商供货排行、药品销售Top10、库存分类占比。每一个图表背后对应一条SQL逻辑简单但对系统演示效果提升明显。做这类聚合查询时最容易出的问题是日期格式的数据库兼容性和空数据缺位。比如某个月没有出入库GROUP BY的结果里就不会有这个月前端图表就会空一个月份。解决办法是在Java端补全月份序列把数据库没返回的月份补成0。这种细节处理能体现你考虑问题的周全度。4.5 前端页面串联与整体部署前端页面规划上我建议按角色动态渲染菜单登录不同的账号看到不同的功能入口。这个动态菜单的前端实现就是在路由配置里根据用户的roleCode字段过滤。如果你是Vue项目打包后集成到SpringBoot时有个关键点把Vue的publicPath改成相对路径或根路径执行npm run build后把dist下的所有文件复制到SpringBoot的static目录然后用mvn clean package打包。运行java -jar浏览器访问端口就能看到完整系统。如果发现页面刷新404通常是前端路由的History模式导致的。两种解法一种是改用Hash模式简单但不美观另一种是在SpringBoot里写一个转发控制器把非/api/开头的路径都转发到index.html。毕设演示我更推荐第一种省时间不折腾。5. 常见问题与排查技巧实录这部分全是真实项目里高频踩坑的总结我直接按问题列表整理每一项都是我见过别人栽过跟头的地方。5.1 SpringBoot启动失败报错指向版本问题最常见的两个场景。第一个是JDK版本和SpringBoot版本不匹配比如用JDK 17跑SpringBoot 2.1.x启动时可能出现奇怪的反射报错。第二个是SpringBoot 3.x项目复制了SpringBoot 2.x的代码代码里全是javax.servlet包找不到。解决办法非常直接统一版本SpringBoot 3.x项目里所有涉及Servlet、注解的地方改成jakarta前缀JDK 8的老项目就用SpringBoot 2.7.x不要盲目用新版框架来“显得高级”。另一个启动问题隐藏在端口占用上。SpringBoot默认端口8080如果本地开了别的服务启动直接报“Port 8080 was already in use”。解决办法是在application.yml里改端口或者用命令查占用端口的进程。我知道第一次遇到这个报错的同学往往会以为是代码问题花半天去排查依赖其实就是一个端口配置的事。5.2 MySQL连接失败时区、驱动、建库三连坑连接MySQL时报Cannot create PoolableConnectionFactory绝大多数原因是三种情况。第一是MySQL 8以上版本要求显式指定时区连接串里要加serverTimezoneAsia/Shanghai第二是没有把驱动依赖加进pom.xml只加了MySQL Connector/J但版本不匹配第三是你在本机根本没有创建对应的数据库连接串里写的库名不存在。建库后还有字符集问题创建数据库时一定用utf8mb4不要只写utf8。utf8mb4能存四字节的emoji和其他特殊字符虽然毕设数据里不一定有但这是专业习惯问题。5.3 前端请求后端全部404或数据传不进去先说最直接的404确认你的Controller路径和前端请求的路径是否完全一致包括大小写、RequestMapping前缀、方法上的路径组合。建议统一在Controller类上加RequestMapping(/api/drug)前端也统一请求/api/drug/xxx不要有些地方带前缀有些地方不带。再说跨域。前后端分离开发时Vue跑在8081SpringBoot跑在8080浏览器会阻止跨域请求。最简单方案是后端加一个CorsFilter配置类允许所有来源的跨域请求。毕设阶段可以全部放开不用太过纠结白名单限制。数据传不进去还有一个极隐蔽的坑前端用application/json格式传JSON字符串后端接收参数的DTO里却没有加RequestBody或者实体类没有无参构造方法。SpringBoot用Jackson反序列化JSON时默认需要对象有无参构造函数你一旦重写了带参构造函数却忘了加无参构造接口就会收到一堆莫名其妙的绑定错误。5.4 定时任务不执行或重复执行一句Scheduled写完后如果发现定时任务没触发常见原因是启动类或配置类上漏了EnableScheduling。很多同学只在方法上加了Scheduled漏掉总开关任务自然不跑。如果发现任务执行了两遍就要检查是不是在多个配置类里都注册了同一个任务或者Spring容器里出现了多个组件实例。排查方法是启动时看日志是否有同一个任务被多次注册的信息。对单机部署的毕设系统用Scheduled完全足够不要为了展示能力去引入Quartz或XXL-Job那是给自己挖坑。5.5 答辩演示最容易翻车的三个点答辩环节和开发阶段关注点完全不一样开发时你关心代码能不能编过答辩时老师关心的是你的系统是否“真的理解了这个业务”。我见过太多演示现场翻车的同学问题往往集中在三点。第一个是演示前没有准备干净的数据。系统里如果全是“测试1、测试2、aaa、bbb”这种数据即使功能正常观感也很差。我建议给药品表和入库单准备一批真实感强的样例数据比如“阿莫西林胶囊 0.25g24粒”“布洛芬缓释胶囊 0.3g20粒”供应商写成常见企业名称这样演示时老师会觉得你做过功课。第二个是对“低库存预警”没有形成演示闭环。好的演示顺序应该是先展示一个库存正常、一个库存低于下限、一个临近有效期的药品状态列表然后做一次入库操作刷新后看到低库存预警消失。这个闭环一演示完整条业务链路就串起来了比你按菜单一个个点过去有力得多。第三个是数据库连接突然挂了。演示现场没网或MySQL服务没有启动前端界面转圈圈场面极度尴尬。有一个比较稳妥的做法提前在application.yml里配置好本机数据库并把MySQL服务设为开机自启演示前一天完整跑一遍打包后的Jar。很多老师喜欢问“你项目里这个接口调用的SQL是什么”你如果能现场打开日志或者控制台看到SQL打印印象分会明显不一样。6. 让系统从“能用”变成“有思考”指导答辩6.1 讲清楚一个模块的正确姿势答辩时介绍模块不要停留在“这个页面可以新增、可以修改、可以删除”这种话是个人都能写。正确讲法是四段式业务背景、设计取舍、核心实现、效果与改进。以入库管理为例。业务背景是药品入库需要记录供应商、批次号、有效期而且一张入库单包含多种药品所以拆成主表和明细表。设计取舍是为什么不在药品表里直接改库存而是先生成入库单再异步更新库存因为直接改库存会导致无法追溯“谁在什么时候进了多少货”。核心实现是事务里先插入主单和明细再根据明细更新药品库存。效果是通过入库单明细能完整追溯每一批药品的来源和效期。这个四段式讲完老师想追问都难。6.2 三个轻量级的加分扩展方向如果你的系统做完了还有余力我建议优先做这三个扩展性价比极高。第一个是Excel导入导出。用EasyExcel库把药品列表导出成Excel或者从Excel模板批量导入药品。这在实际工作场景里极其常用而且EasyExcel封装得很好几十行代码就能实现。答辩时展示一下批量导入一页药品数据比单个添加有说服力得多。第二个是批量出库和批次优先策略。出库时选择哪个批次的药品可以做一个智能排序临期批次排在前面实现先进先出。这不需要复杂算法就是排序规则和库存扣减逻辑的结合但能直接体现你对药品效期管理的理解。第三个是库存盘点。新建盘点单选择某个药品分类录入实盘数量系统自动对比账面数量和实盘数量生成盘盈盘亏明细。这个模块通过一张盘点表加一个对比逻辑就能实现但对系统的业务完整度提升明显。6.3 代码里绝不能出现的四类痕迹代码整洁度是老师翻项目源码时的隐性评分项。四种最掉分的痕迹是注释里残留“以下代码从网上复制”、一整段几百行的Controller方法没有任何分层、实体类里出现意义不明的a, b, c字段名、异常全部e.printStackTrace()后继续执行且没有任何日志记录。写代码时顺手把工具类抽取出来、把字段命名为有业务含义的单词、在Controller层做参数校验而不是让脏数据一路穿透到数据库这些习惯不需要额外花多少时间但整体观感完全不一样。回到题目本身药品管理系统这个毕设项目真正拉开差距的从来不是“会不会用SpringBoot”而是“有没有用工程化思维把业务流、数据流、权限流串起来”。如果你能按照这篇文章的思路把表结构理清楚把库存预警、效期管理、统计报表这三块顺下来再在答辩时把你做过的每一个取舍决定讲出理由这个题目基本就稳了。我个人带项目的习惯是每一步都问自己“为什么这么设计”把这些答案写在项目说明里答辩那天自然不慌。