做课设/毕设的时候,选“智能药箱系统”这种题目的人不少,但很多人拿到源码后反而更慌:药箱和进销存明明是两套东西,怎么揉进一个系统里?库存怎么算?预警怎么做?文档和演示怎么讲才能让答辩评委觉得“这系统确实是你的”?这篇文章我会把这套基于 Spring Boot 的智能药箱与医药进销存管理系统从头到尾拆开讲,重点放在业务逻辑、数据库建模、核心代码实现、预警模块设计和答辩准备上,给准备做这类项目的同学一条能直接照做的路线。
1. 先搞清楚这套系统的双重业务逻辑:药箱节点和进销存链路各管什么
很多人一上来就写代码,结果写着写着就乱了。因为“智能药箱系统”和“医药进销存管理系统”虽然放在同一个标题下,但它们其实是两层逻辑:一层是药箱这个物理节点的管理,另一层是药品从供货商到药箱再到患者的流通管理。这两层必须合在一起理解,才能把表结构设计对。
1.1 药箱不是“仓库”,是带编号的物理节点
传统进销存系统里管的是“仓库”或“库房”,所有库存挂在仓库编号下面。但智能药箱系统不一样,它把存储单元拆成了多个药箱。每个药箱有自己的编号、名称、安装位置、温湿度阈值等属性。药品入库时,要明确入到哪个药箱;出库时,也要从指定药箱扣减。
这意味着库存表里必须有一个“药箱维度”。设计表时我会在库存表里同时放drug_id和box_id,而不是只放一个仓库字段。这样系统扩展起来也方便,比如以后要支持免费领药、科室领用、多药箱联动,只需要在药箱表里加记录就行。
1.2 药品流通链路:采购、入库、出库、报损是一条锁链
进销存的核心不是增删改查,而是“单据驱动库存变化”。以这个药箱系统为例,药品进入药箱之前,要先有“采购单”。采购单审核通过之后,系统才会增加对应药箱的库存。药品离开药箱时,要有“出库单”或“领用单”,系统才允许扣减库存。
这里特别重要的一点是:库存变动必须和单据绑定。也就是说,你不能直接在页面里“改一下库存数量”就当入库了,必须通过采购单审核、出库单提交这样的动作触发库存变动。每个动作都会产生一条库存流水记录,方便以后对账、排查问题。这也是进销存系统和普通 CRUD 系统最本质的区别。
1.3 用户角色拆分:管理员、药箱负责人、普通用户
这套系统的用户角色我建议至少分三类,哪怕功能做得简单,角色边界也要清晰。管理员负责用户管理、供货商管理、数据统计;药箱负责人负责日常采购、入库、出库、报损操作;普通用户可以查询药品库存、查看预警信息,在简化场景下可以模拟“领药”操作。
这样做的好处是:论文里可以画出用例图,答辩时可以讲清楚权限控制的必要性,系统代码里也可以用拦截器或注解简单控制访问权限。功能不需要很复杂,但业务闭环必须是完整的。
2. 技术选型的落地考量:Spring Boot 全家桶到底比别的方案强在哪
技术选型这部分,很多同学会纠结要不要上 Vue 前后端分离、要不要用微服务。我的建议很直接:做课设/毕设,Spring Boot + MyBatis-Plus + MySQL + Thymeleaf 这套组合是效率和稳定性兼顾的选择,没必要为了炫技引入一堆不必要的复杂度。
2.1 为什么是 Spring Boot + MyBatis-Plus,而不是原生 SSM 或 JPA
原生 SSM 的配置文件太多,光 Spring、Spring MVC、MyBatis 三个框架的 XML 配置就能折腾大半天,而且年代感很重。JPA/Hibernate 虽然写起来省代码,但很多同学对它的级联和懒加载机制不熟,排查问题反而更费劲。
MyBatis-Plus 刚刚好:它保留了 MyBatis 的手写 SQL 能力,又提供了BaseMapper自带单表 CRUD、分页插件、逻辑删除、条件构造器等能力。做进销存这类业务,单表 CRUD 和复杂统计 SQL 是混着来的,MyBatis-Plus 能让你把大部分简单操作省掉,把精力放在库存扣减、预警扫描这类核心逻辑上。
2.2 依赖引入与基础配置
以下依赖组合我在项目里实际用过,稳定跑通没有问题:Spring Boot 2.7.x 搭配 JDK 8,或者 Spring Boot 3.x 搭配 JDK 17,二选一即可。如果学校机房电脑环境老旧,建议优先选 JDK 8 + Spring Boot 2.7.x,兼容性最好。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>application.yml里有一处很容易踩坑,就是 MySQL 连接串的时区参数。如果不加serverTimezone=Asia/Shanghai,某些版本的驱动会在连接时报时区错误,或者在日期字段上出现偏差。下面是一份能直接用的配置:
spring: datasource: url: jdbc:mysql://localhost:3306/smart_medicine?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver thymeleaf: cache: false mybatis-plus: configuration: map-underscore-to-camel-case: true global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 02.3 拿到源码后的第一步不是改代码,而是先画包结构图
我见过太多同学下载源码后直接启动,然后发现页面报错、数据不对,却不知道从哪里开始排查。正确做法是:先把项目包结构打开,画出请求大致走向。以这套系统为例,包结构通常是:
controller:接收前端请求,参数校验service:业务逻辑,比如采购审核、出库扣库存mapper:数据库操作entity:实体类,对应表结构dto:前端传参对象,避免直接拿实体类接收config:拦截器、分页插件、跨域配置等common:统一返回结果、异常类、工具类
当你看到一个新增采购单的请求时,要能说出它从 Controller 走到哪个 Service、再走到哪张表。路线理清楚之后,改代码、加功能才不会被绕晕。
3. 数据库建模的第一原则:把“批号”和“药箱位置”这两个字段想明白
数据库是整个项目的地基,表结构一旦建错,后面所有业务逻辑都会跟着别扭。智能药箱进销存系统的核心表结构,不是简单把“药品表”和“订单表”建出来就完事,而是要处理好“一行库存记录到底代表什么”这个问题。
3.1 一行库存记录 = 药品 + 批号 + 药箱位置 + 剩余数量
在药品库存表里,我建议把drug_id、box_id、batch_no三个字段作为联合维度,再加一个库存数量stock。也就是说,同一款药品如果批号不同,或者所在药箱不同,它们在库存表里就是两行不同记录。
为什么要这么设计?因为药品的特殊性在于批号和有效期。同一款药,批号 A 可能还有 200 天到期,批号 B 可能还有 20 天到期。出库的时候必须优先出效期更早的那批药,也就是“先进先出”。如果库存表里不区分批号,价格和效期就没法管理,很容易出问题。
如果做的是“多药箱”场景,这个设计就更重要了:一楼急救药箱和二楼主备药箱里可能都有同一种药,但数量、批次完全不同,必须分开记账。
3.2 核心表结构清单与分析
下面这张表是我整理的一套相对完整的核心表结构,适合课设/毕设场景,既不会太复杂,也能覆盖完整的进销存流程。
| 表名 | 中文名 | 作用说明 |
|---|---|---|
| sys_user | 系统用户表 | 管理员、药箱负责人、普通用户 |
| drug_info | 药品信息表 | 通用名、商品名、规格、厂家、批准文号等 |
| drug_box | 智能药箱表 | 药箱编号、位置、温湿度阈值、状态 |
| drug_stock | 药品库存表 | 药品+批号+药箱+数量+生产日期+有效期 |
| stock_record | 库存流水表 | 每次入库/出库/报损的变动明细 |
| supplier | 供货商表 | 供货商名称、联系人、电话 |
| purchase_order | 采购单主表 | 采购单号、供货商、总金额、状态、审核人 |
| purchase_order_item | 采购单明细表 | 采购的药品、数量、单价、批号、效期 |
| sale_order | 出库/领用单主表 | 出库单号、操作人、药箱、备注 |
| sale_order_item | 出库/领用明细表 | 出库药品、数量、批号 |
| drug_warning | 预警记录表 | 近效期、过期、库存不足、温湿度预警记录 |
| operation_log | 操作日志表 | 记录关键操作的审计日志 |
3.3 几个容易踩的建模坑
第一个坑是库存表没有唯一索引。理论上同一药箱内相同批号的药品应该合并为一行,如果不对drug_id + box_id + batch_no建唯一索引,并发操作时很容易出现重复行、数量混乱。建表时最好直接把这个联合唯一索引加上。
第二个坑是效期字段用了 datetime。药品有效期只需要精确到天,用date类型就够了,不要用datetime。用 datetime 后,判断“是否过期”时会引入时分秒的边界问题,比如某药品有效期到当天,你到底是当天零点之前算有效还是当天结束前算有效。用 date 类型只比较日期,逻辑简洁很多。
第三个坑是流水表不可删除。有的同学把stock_record设计成可以修改、可以删除,这等于给库存数据留了后门。流水表应该只允许插入和查询,不允许修改和删除,业务上要掉坑时才能追溯。至于逻辑删除字段,普通业务表可以加,但库存流水表我建议不加,避免逻辑删除与唯一索引产生二次冲突。
4. 库存流水是系统的命根子:入库、出库、报损的核心实现
这一章是整个代码实现里最硬核的部分,也是答辩时最容易被追问的地方。库存流水的设计逻辑是:每一次库存变化,都必须有对应的单据记录和流水记录,数量一变,流水一定同步写入。
4.1 采购入库流程:采购单状态流转带动库存增加
采购入库不能直接“改库存”。正确流程是:先创建采购单,明细里填写药品、数量、单价、批号、生产日期、有效期。采购单初始状态为“待审核”。管理员审核通过后,系统遍历采购明细,在库存表里查找是否已有同药箱、同批号的记录,如果有就在原数量上加,如果没有就新插入一条记录。然后写库存流水。
purchase_order的状态字段建议用数字枚举,比如 0 草稿、1 待审核、2 已入库、3 已作废。这样在列表页可以用一个下拉框筛选,接口实现逻辑也非常清晰。
核心入库代码大致是这个意思:
@Transactional(rollbackFor = Exception.class) public void auditAndStockIn(Integer purchaseOrderId) { PurchaseOrder order = purchaseOrderMapper.selectById(purchaseOrderId); if (!"PENDING".equals(order.getStatus())) { throw new BizException("当前采购单状态不可入库"); } List<PurchaseOrderItem> items = purchaseOrderItemMapper.selectList( new LambdaQueryWrapper<PurchaseOrderItem>() .eq(PurchaseOrderItem::getOrderId, purchaseOrderId)); for (PurchaseOrderItem item : items) { // 先查库存表,有则累加,无则新增 DrugStock stock = stockMapper.selectOne(new LambdaQueryWrapper<DrugStock>() .eq(DrugStock::getDrugId, item.getDrugId()) .eq(DrugStock::getBoxId, item.getBoxId()) .eq(DrugStock::getBatchNo, item.getBatchNo())); if (stock == null) { stock = new DrugStock(); stock.setDrugId(item.getDrugId()); stock.setBoxId(item.getBoxId()); stock.setBatchNo(item.getBatchNo()); stock.setStock(item.getQuantity()); stockMapper.insert(stock); } else { stock.setStock(stock.getStock() + item.getQuantity()); stockMapper.updateById(stock); } // 写流水 StockRecord record = buildStockRecord(item, STOCK_IN, item.getQuantity(), null, stock.getStock()); stockRecordMapper.insert(record); } order.setStatus("DONE"); purchaseOrderMapper.updateById(order); }注意整个方法加了@Transactional,否则一旦中途抛异常,库存加了一半,流水写了一半,数据就乱了。
4.2 出库扣减库存:用原子 SQL 而不是先查询再扣减
很多新手写扣库存是先select出来,判断数量够不够,再update。这在并发场景下容易出现“超卖”。虽然课设系统并发量不大,但答辩时被问到“并发下怎么保证库存不超扣”是常见问题,最好一开始就用数据库原子操作来写。
扣减库存最稳妥的写法是,在DrugStockMapper里自定义一条乐观式的原子 SQL:
@Mapper public interface DrugStockMapper extends BaseMapper<DrugStock> { @Update("UPDATE drug_stock SET stock = stock - #{quantity} " + "WHERE id = #{id} AND stock >= #{quantity}") int deductStock(@Param("id") Integer id, @Param("quantity") Integer quantity); }这条 SQL 的含义是“只有当前库存大于等于扣减数量时,才执行扣减”,数据库层面的行锁保证同一条记录不会同时被两个请求扣超。返回值是受影响行数,如果返回 0,说明库存不足,业务层直接抛出提示。
出库 Service 层的大致逻辑:
@Transactional(rollbackFor = Exception.class) public void createOutOrder(SaleOrderBO bo) { // 1. 创建出库单主表,状态为已出库 SaleOrder order = new SaleOrder(); order.setBoxId(bo.getBoxId()); order.setOperatorId(getCurrentUserId()); order.setTotalAmount(bo.getTotalAmount()); saleOrderMapper.insert(order); // 2. 遍历明细,逐个扣减库存、写流水 for (SaleOrderItemBO item : bo.getItems()) { int rows = drugStockMapper.deductStock(item.getStockId(), item.getQuantity()); if (rows == 0) { throw new BizException("库存不足或药品不存在:" + item.getDrugName()); } DrugStock stock = drugStockMapper.selectById(item.getStockId()); stockRecordMapper.insert(buildStockRecord(item, STOCK_OUT, item.getQuantity(), stock.getStock())); // 3. 扣减后检查是否低于库存下限,若低于则生成预警 checkMinStockAndWarn(stock); } }4.3 报损和退货:库存调整的另外两个出口
过期药品、破损药品不能走正常出库流程,要有独立的报损单。报损单记录药品、批号、数量、原因。审核通过后,扣减库存,流水类型记为“报损”。退货逻辑则是在采购入库后发现药品质量问题,需要退回供货商,流水类型记为“退货”。
如果不想单独建报损表和退货表,可以统一用“库存调整单”来实现,调整类型字段区分报损、退货、盘点修正。对课设来说,用一个stock_change单表和对应明细表也完全够用,关键在于要把调整原因写清楚,流水表里能看到每一种库存变动的来龙去脉。
5. 智能提醒模块:近效期预警与库存下限扫描的实现思路
“智能”两个字最直接的落点,就是预警提醒模块。这部分做好了,论文和演示都会加分很多,因为它能体现你并不只会写增删改查,而是理解业务场景的“主动服务”能力。
5.1 预警规则与触发频率设计
我建议系统支持下面几种预警:
| 预警类型 | 触发条件 | 建议级别 |
|---|---|---|
| 近效期预警 | 有效期剩余天数 <= 90 天 | 黄色 |
| 过期预警 | 有效期已经早于当天 | 红色 |
| 库存过少预警 | 库存数量 <= 药箱中该药品的库存下限 | 黄色 |
| 库存积压预警 | 库存数量 >= 药品库存上限 | 蓝色 |
| 温湿度模拟预警 | 药箱温湿度超过配置阈值 | 红色 |
触发方式设计成“定时扫描 + 页面实时检查”双通道。定时扫描用 Spring 自带的@Scheduled注解就可以,每天凌晨跑一次全量检查并写入预警表。页面打开时,实时查询预警表,把未处理的预警显示在导航栏和首页。
@Component public class DrugWarningTask { @Scheduled(cron = "0 0 2 * * ?") public void dailyScan() { List<DrugStock> stocks = drugStockMapper.selectList(null); for (DrugStock stock : stocks) { handleExpireWarning(stock); handleMinStockWarning(stock); } handleBoxEnvWarning(); } private void handleExpireWarning(DrugStock stock) { long remainDays = ChronoUnit.DAYS.between( LocalDate.now(), stock.getExpireDate()); if (remainDays < 0) { saveWarning(stock, "OVERDUE", "药品已过期"); } else if (remainDays <= 90) { saveWarning(stock, "NEAR_EXPIRE", "药品剩余效期不足" + remainDays + "天"); } } private void saveWarning(DrugStock stock, String type, String content) { long exists = drugWarningMapper.selectCount(new LambdaQueryWrapper<DrugWarning>() .eq(DrugWarning::getStockId, stock.getId()) .eq(DrugWarning::getType, type) .eq(DrugWarning::getStatus, "UN_HANDLED")); if (exists == 0) { DrugWarning warning = new DrugWarning(); warning.setStockId(stock.getId()); warning.setType(type); warning.setContent(content); warning.setStatus("UN_HANDLED"); drugWarningMapper.insert(warning); } } }这段代码做了两个关键动作:一是计算剩余效期天数并分类,二是防止重复生成预警,如果同一批次药品已经有未处理的同类预警,就不重复插入。
5.2 预警不只是“提醒”,还要绑定处理动作
预警记录表最好有“状态”和“处理结果”字段。药箱负责人看到预警后,可以点击“处理”按钮,选择“清点库存”“下架过期药”“退回供货商”等动作。系统自动把处理结果写到预警记录里。
这样做的好处是,演示时可以完整地走一遍“系统发现近效期药品 → 负责人确认 → 下架/处理 → 预警解除”的流程。评委看到这个闭环,对系统的评分会明显高于单纯的列表式提醒。如果没有这个状态流转,预警就只是没有落地功能的“摆设”。
5.3 过期货品如何强制冻结
预警只是提示,真正要保证安全,出库逻辑里还必须再做一道防线。建议在库存表里加一个available字段,当定时任务发现某条库存记录已过期时,自动把available置为 0。出库时只允许查到available=1的库存记录,这样就算有人绕过页面的提示,也无法把过期药出出去。
这个机制非常值得写入论文和答辩说明:你不仅“提醒”了用户,还在系统层面“强制阻断”了风险操作。这比单纯做个预警表有说服力得多。
6. 演示级的页面设计:让答辩评委一眼看懂业务闭环
如果你的系统做完之后,演示时只在列表页里点来点去,评委很可能觉得你只是做了几个 CRUD 页面。为了让项目的“智能药箱 + 进销存”价值真正被看见,前端页面需要有意识地设计成交互闭环。不需要花哨,但每一步操作都要有页面反馈和数据联动。
6.1 工作台首页:一屏展示核心状态
登录后的首页不要放欢迎语和无意义的图片,要放关键业务数据。推荐放这几块:
- 顶部统计卡片:药品总数、药箱总数、库存总量、今日预警数
- 近效期药品表格:展示最紧急的 5 条记录,按剩余天数升序排列
- 低库存药品表格:展示库存低于下限的药品
- 最近出入库记录:展示最近几次入库、出库流水
使用 ECharts 或者 Chart.js 画一个“近 7 天出入库趋势”折线图,页面会立刻显得完整很多。这种图看起来复杂,实现其实只需要查一张流水表再按日期聚合,半天就能搞定。
6.2 采购入库和出库页面的操作路径设计
采购页面建议拆成两个步骤:第一步选供货商、填采购单基础信息;第二步在明细区域动态添加药品行,选择药品、填写数量和批号。如果不清楚怎么写动态行,最省事的方式是先用列表页固定几行输入,提交时组装成 JSON 数组,controller里用List<PurchaseOrderItemBO>接收。
出库页面最好按“选药箱 → 查库存 → 选批次 → 输数量 → 提交”设计。先选药箱,查询该药箱里的库存列表,然后针对某条药品选择出库数量和点击“出库”。这样每次出库都明确对应“从哪个药箱、哪个批次出库”,库存扣减逻辑和页面操作是对应的,演示时讲起来非常顺。
6.3 演示流程的脚本化准备
不管系统功能做得多全,演示时都要有一条主线。我建议按照下面的顺序准备演示用例:
- 登录系统,展示工作台首页,让评委看到预警数量和库存汇总
- 进入预警列表,找到一条近效期预警
- 模拟采购入库一批新药,展示库存数量增加、流水记录生成
- 模拟出库操作,展示库存数量减少、流水记录生成
- 回到工作台,展示出入库趋势图和新的预警状态
演示之前,往数据库里人为插入几条“快到期”“库存低于下限”的测试数据,让预警模块一打开就有内容可看。演示数据和真实数据混在一起的观感远比空列表好。开始演示时先切到工作台页,再点进预警列表,节奏会显得很专业。
7. 论文/文档撰写与答辩避坑清单
“附万字文档”这个标签说明文档在项目里占了很高的权重。论文不仅仅是应付查重,更是为自己梳理系统逻辑的工具。文档写得到位,答辩时心里就有底。
7.1 万字文档的骨架与写作顺序
建议按下面的章节顺序写,每个章节需要写成“背景 + 现状 + 问题 + 本系统怎么做”四段式:
- 第一章 绪论:写医药流通管理背景和智能药箱的发展趋势,介绍系统中要用到的主流技术框架
- 第二章 相关技术介绍:重点写 Spring Boot、MyBatis-Plus、MySQL、模板引擎,每项技术写清楚选它的原因
- 第三章 需求分析:画系统用例图,分析功能需求和非功能需求,列功能模块清单
- 第四章 系统设计:画整体架构图、模块功能结构图、数据库 ER 图,逐表说明核心字段
- 第五章 系统实现:按模块放页面截图和关键代码,代码必须配合业务逻辑解释
- 第六章 系统测试:写测试计划、测试用例表、测试结果与分析,列几个核心流程的操作步骤
- 第七章 总结与展望:写项目完成情况、不足点、后续能优化的方向,这部分反而要实在
画图建议用 Draw.io 或 ProcessOn 这类在线工具。用例图、流程图、ER 图三张图必须清晰好看,这是论文评阅和答辩时的第一印象来源。
7.2 论文里放哪段代码、放多少代码
论文里不要贴整段代码,尤其不要贴 Controller 或 Mapper 那种重复性很高的代码。最有价值的片段是这几段:
- 出库扣减库存的原子 SQL 和事务方法
- 预警扫描的定时任务逻辑
- 采购入库时批号匹配与库存累加的循环逻辑
每一段代码旁边,加两三句“为什么这么写”的说明。评阅老师最想看到的是“作者理解自己在写什么”,而不是代码搬运工。哪怕你写的代码风格简单,只要你能在论文里把逻辑讲清楚,效果就远好于大段代码无人解说。
7.3 答辩高频问题与应答思路
根据我的经验,这种系统的答辩问题通常集中在几个点上,提前准备好应答思路就不会慌。
第一个必问题:库存扣减怎么防止超卖?回答思路是先说用了事务保证多表一致性,再说扣减采用数据库原子更新 SQL,最后补充一句“如果返回影响行数为 0 则说明库存不足,直接中断操作”。把这三层说法讲完,评委基本不会再追问。
第二个问题:预警为什么能自动执行?回答思路是项目里用了 Spring 定时任务,配置 cron 表达式每天凌晨扫描一次;同时入库和出库的 Service 方法里也会实时触发库存下限检查。定时任务加实时触发的双通道设计,是比较完整的回答。
第三个问题:药品批次怎么处理?回答思路是库存表以“药品 + 批号 + 药箱”三个字段作为唯一记录维度,不同批号不同效期的药品分开存储,出库时优先匹配最早效期的可用批次。最好顺手在数据库里查一条数据给评委看。
7.4 把“智能药箱”亮点写大,避免答辩被说成普通进销存
如果答辩时评委的第一反应是“这不就是一个普通进销存系统吗”,那说明你文档和演示里没有突出智能药箱的差异化价值。建议从两处强化:一是在系统设计里加入“多药箱管理”“药箱位置管理”和“温湿度预警模拟”内容,页面也要体现这些实体;二是在论文创新点里重点描述“智能化预警”,包括效期自动计算、近效期自动提醒、过期药品自动冻结出库。这套逻辑是从药品安全角度出发的,天然有业务价值和故事可讲。
答辩时主动提一句“传统进销存只管库存数量变化,没有效期维度的自动监控,本系统把药箱节点和效期监控并入到进销存链路中”,这个点就能明显区分出你和那些只做基础 CRUD 的同学。
我个人的体会是,做这类毕设项目,最大的门槛往往不是框架不会用,而是对业务逻辑的理解停留在“建表 + 接口”的层面。只要把那两条主线想清楚——一条是采购到出库的流通线,一条是库存到效期的监控线——系统设计、页面演示和论文文档都会变得非常顺。给读者一个实际可落地的小建议:动手之前,先把文中的表结构设计拿草图画一遍,然后把库存流水表的核心字段自己默写一遍,再把出入库的三步流程讲给室友听。能清晰讲出来,就说明你离顺利答辩已经不远了。