医院药房药品库存管理系统:从CRUD到企业级优化的实战指南

📅 2026/8/24 5:30:33
医院药房药品库存管理系统:从CRUD到企业级优化的实战指南
最近在辅导学生毕业设计和课程设计时发现很多同学在开发“医院药房药品库存管理系统”这类实战项目时常常陷入一个误区只实现了基础的增删改查CRUD功能就认为项目完成了。实际上一个真正有价值、能体现技术深度的管理系统其核心在于功能优化与工程化实践。本文将以一个完整的“医院药房药品库存管理系统”为例深度剖析如何从一个基础模型出发进行全方位的功能优化与系统增强最终打造一个具备高可用性、安全性和可维护性的实战项目。本文不仅提供完整的源码、数据库设计文档和部署报告还会详细讲解关键代码的实现逻辑与优化思路旨在为你的毕业设计、课程设计或企业级项目开发提供一份可直接复用的“保姆级”指南。1. 系统背景与核心需求分析在开始编码之前明确系统的业务边界和核心需求是至关重要的。一个典型的医院药房药品库存管理系统其核心目标是确保药品供应的准确性、及时性和安全性同时优化药房工作人员的工作流程。1.1 传统管理模式的痛点库存不准手工记账易出错导致账面库存与实际库存不符可能影响临床用药。效期管理困难药品批次多效期分散人工盘点效率低下容易造成药品过期浪费。流程繁琐药品的入库、出库、退库、盘点等操作依赖纸质单据流转慢查询历史记录困难。信息孤岛药房数据与采购、财务、临床科室数据脱节难以进行全局分析和决策支持。1.2 优化后系统的核心目标我们的优化方向正是为了解决上述痛点将系统从一个简单的“记录本”升级为智能的“管理助手”精准化库存管理实现实时库存跟踪支持批次管理和先进先出FIFO策略。自动化预警机制对库存不足、库存积压、近效期药品进行自动预警。流程电子化与规范化将各类业务单据电子化并固化审批流程确保操作可追溯。数据可视化与决策支持通过图表展示库存、消耗等关键指标为管理决策提供数据依据。系统安全与权限控制区分药房管理员、普通药师、采购员等角色实现基于角色的访问控制RBAC。2. 技术选型与环境准备一个稳健的技术栈是项目成功的基石。我们选择当前企业级开发中主流、成熟且生态丰富的技术组合。2.1 技术栈说明后端框架Spring Boot 2.7.x。它简化了Spring应用的初始搭建和开发过程提供了内嵌的Tomcat服务器让我们能快速构建独立运行的、生产级别的应用。持久层框架MyBatis-Plus 3.5.x。在MyBatis的基础上只做增强不做改变提供了强大的CRUD操作和条件构造器能极大减少开发工作量。数据库MySQL 8.0。关系型数据库的经典选择事务支持完善社区活跃。我们将使用InnoDB存储引擎以保证事务安全。前端框架Vue 3 Element Plus。Vue 3的响应式和组合式API让前端开发更灵活高效Element Plus提供了丰富且美观的UI组件能快速构建管理后台界面。构建与依赖管理Maven 3.8后端Node.js 16 npm / yarn前端。开发工具IntelliJ IDEA后端Visual Studio Code前端Navicat或DBeaver数据库。2.2 项目初始化与结构首先我们使用Spring Initializr快速生成后端项目骨架并整合前端。后端项目结构 (pharmacy-inventory-backend)src/main/java/com/pharmacy/ ├── PharmacyInventoryApplication.java // 启动类 ├── config/ // 配置类如Mybatis-Plus, 跨域 安全 ├── controller/ // 控制层接收前端请求 ├── entity/ // 实体类对应数据库表 ├── mapper/ // MyBatis Mapper接口 ├── service/ // 业务逻辑层接口 │ └── impl/ // 业务逻辑层实现 ├── dto/ // 数据传输对象用于前后端交互 ├── vo/ // 视图对象用于返回给前端的数据封装 └── common/ // 通用类如统一响应结果、常量、工具类 resources/ ├── application.yml // 主配置文件 └── mapper/ // MyBatis XML映射文件如果使用使用Spring Initializr创建项目时勾选以下依赖Spring Web,Lombok,MyBatis Framework,MySQL Driver。创建完成后在pom.xml中手动添加MyBatis-Plus依赖。!-- pom.xml 中添加 MyBatis-Plus 依赖 -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3/version /dependency !-- 代码生成器可选用于快速生成基础代码 -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-generator/artifactId version3.5.3/version /dependency前端项目初始化在另一个目录下使用Vue CLI或Vite创建Vue3项目并引入Element Plus和Axios。# 使用 Vite 创建项目推荐 npm create vuelatest pharmacy-inventory-frontend # 按照提示选择项目特性记得加入 Router, Pinia cd pharmacy-inventory-frontend npm install # 安装 Element Plus 和 Axios npm install element-plus axios npm install --save-dev element-plus/icons-vue3. 核心数据库设计与优化数据库设计是系统的“地基”良好的设计能支撑复杂的业务逻辑并保证性能。我们设计以下核心表并着重说明优化点。3.1 核心表结构设计-- 药品信息表 (medicine) CREATE TABLE medicine ( id bigint NOT NULL AUTO_INCREMENT COMMENT 药品ID, code varchar(64) NOT NULL COMMENT 药品编码唯一, name varchar(255) NOT NULL COMMENT 药品通用名, spec varchar(255) DEFAULT NULL COMMENT 规格如0.5g*24片, manufacturer varchar(255) DEFAULT NULL COMMENT 生产厂家, unit varchar(32) NOT NULL COMMENT 最小单位如盒、瓶、支, category varchar(64) DEFAULT NULL COMMENT 药品分类如抗生素、心脑血管, price decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT 单价, stock_warning_min int DEFAULT 0 COMMENT 库存最低预警线, stock_warning_max int DEFAULT 0 COMMENT 库存最高预警线积压预警, status tinyint DEFAULT 1 COMMENT 状态1-正常0-停用, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_code (code), KEY idx_category (category), KEY idx_name (name) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT药品基础信息表; -- 药品批次库存表 (medicine_batch) -- 【核心优化表】 CREATE TABLE medicine_batch ( id bigint NOT NULL AUTO_INCREMENT, medicine_id bigint NOT NULL COMMENT 关联药品ID, batch_no varchar(128) NOT NULL COMMENT 生产批号, production_date date NOT NULL COMMENT 生产日期, expiry_date date NOT NULL COMMENT 有效期至, purchase_price decimal(10,2) DEFAULT NULL COMMENT 采购价, supplier varchar(255) DEFAULT NULL COMMENT 供应商, current_quantity int NOT NULL DEFAULT 0 COMMENT 当前库存数量, lock_quantity int NOT NULL DEFAULT 0 COMMENT 锁定数量已分配未出库, available_quantity int GENERATED ALWAYS AS (current_quantity - lock_quantity) VIRTUAL COMMENT 可用数量虚拟列, storage_location varchar(255) DEFAULT NULL COMMENT 库位, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_medicine_batch (medicine_id,batch_no), -- 同一药品批号唯一 KEY idx_expiry (expiry_date), -- 效期查询优化 KEY idx_medicine (medicine_id), CONSTRAINT fk_batch_medicine FOREIGN KEY (medicine_id) REFERENCES medicine (id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT药品批次库存表实现先进先出; -- 入库单表 (stock_in) CREATE TABLE stock_in ( id bigint NOT NULL AUTO_INCREMENT, in_order_no varchar(64) NOT NULL COMMENT 入库单号规则IN20240520001, supplier varchar(255) DEFAULT NULL, in_type tinyint NOT NULL COMMENT 入库类型1-采购入库2-退药入库3-盘盈入库, total_amount decimal(12,2) DEFAULT 0.00 COMMENT 总金额, handler_id bigint DEFAULT NULL COMMENT 操作人ID, handler_name varchar(64) DEFAULT NULL COMMENT 操作人姓名, status tinyint DEFAULT 1 COMMENT 状态1-待审核2-已入库3-已取消, remark varchar(500) DEFAULT NULL, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_in_order_no (in_order_no), KEY idx_create_time (create_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT入库单主表; -- 入库单明细表 (stock_in_detail) -- 【核心优化关联批次】 CREATE TABLE stock_in_detail ( id bigint NOT NULL AUTO_INCREMENT, in_order_id bigint NOT NULL COMMENT 关联入库单ID, medicine_id bigint NOT NULL COMMENT 药品ID, batch_no varchar(128) NOT NULL COMMENT 批号, production_date date NOT NULL, expiry_date date NOT NULL, quantity int NOT NULL COMMENT 入库数量, unit_price decimal(10,2) NOT NULL COMMENT 单价, total_price decimal(10,2) GENERATED ALWAYS AS (quantity * unit_price) VIRTUAL, storage_location varchar(255) DEFAULT NULL, PRIMARY KEY (id), KEY idx_in_order (in_order_id), KEY idx_medicine (medicine_id), CONSTRAINT fk_detail_in FOREIGN KEY (in_order_id) REFERENCES stock_in (id) ON DELETE CASCADE, CONSTRAINT fk_detail_medicine FOREIGN KEY (medicine_id) REFERENCES medicine (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT入库单明细表;注出库单stock_out、盘点单stock_take等表结构类似此处省略。出库单明细stock_out_detail需要关联消耗的特定批次库存。3.2 数据库设计优化点解析批次管理medicine_batch表是核心优化。它记录了每个批次的效期、采购价、供应商。available_quantity可用数量通过生成列自动计算确保数据一致性。出库时可以按照expiry_date排序优先出库效期早的批次实现先进先出(FIFO)。库存锁定lock_quantity字段用于处理“已开单未发药”的场景。当医生开具处方后系统先锁定对应批次的库存防止被其他处方占用。发药完成后再扣减current_quantity并清零lock_quantity。这解决了并发下的库存超卖问题。单据流水号使用如IN20240520001格式的单号包含业务类型和日期便于查询和归档。可以在应用层使用Redis分布式锁或数据库序列来生成确保唯一性。索引优化在medicine_batch(expiry_date)上建立索引加速近效期查询。在medicine(code, name, category)上建立索引加速药品检索。4. 后端核心功能实现与优化我们以药品入库和库存查询与预警这两个核心且能体现优化思想的模块为例讲解后端代码实现。4.1 药品入库服务实现含事务与批次管理入库不仅仅是增加库存数量更重要的是创建或更新批次信息。// 文件路径src/main/java/com/pharmacy/service/impl/StockInServiceImpl.java Service Transactional(rollbackFor Exception.class) // 声明式事务管理 Slf4j public class StockInServiceImpl implements StockInService { Autowired private StockInMapper stockInMapper; Autowired private StockInDetailMapper detailMapper; Autowired private MedicineBatchMapper batchMapper; Autowired private MedicineMapper medicineMapper; Override public boolean saveStockInWithDetail(StockInDTO dto) { // 1. 参数校验 if (dto.getDetails() null || dto.getDetails().isEmpty()) { throw new BusinessException(入库明细不能为空); } // 2. 生成入库单号 (示例使用时间戳随机数生产环境建议用更健壮的方式) String inOrderNo IN LocalDateTime.now().format(DateTimeFormatter.ofPattern(yyyyMMddHHmmss)) ThreadLocalRandom.current().nextInt(100, 999); // 3. 保存入库单主表 StockIn stockIn new StockIn(); BeanUtils.copyProperties(dto, stockIn); stockIn.setInOrderNo(inOrderNo); stockIn.setStatus(1); // 待审核 stockInMapper.insert(stockIn); // 4. 遍历明细保存明细并更新批次库存 for (StockInDetailDTO detailDto : dto.getDetails()) { // 4.1 保存明细 StockInDetail detail new StockInDetail(); BeanUtils.copyProperties(detailDto, detail); detail.setInOrderId(stockIn.getId()); detailMapper.insert(detail); // 4.2 更新或插入批次库存信息 【核心逻辑】 LambdaQueryWrapperMedicineBatch queryWrapper new LambdaQueryWrapper(); queryWrapper.eq(MedicineBatch::getMedicineId, detailDto.getMedicineId()) .eq(MedicineBatch::getBatchNo, detailDto.getBatchNo()); MedicineBatch existingBatch batchMapper.selectOne(queryWrapper); if (existingBatch ! null) { // 批次已存在更新库存数量 existingBatch.setCurrentQuantity(existingBatch.getCurrentQuantity() detailDto.getQuantity()); // 可以在这里加入效期校验如果新入库的效期更早是否要更新批次信息 batchMapper.updateById(existingBatch); log.info(更新药品[ID:{}]批次[{}]库存增加数量{}, detailDto.getMedicineId(), detailDto.getBatchNo(), detailDto.getQuantity()); } else { // 创建新批次 MedicineBatch newBatch new MedicineBatch(); newBatch.setMedicineId(detailDto.getMedicineId()); newBatch.setBatchNo(detailDto.getBatchNo()); newBatch.setProductionDate(detailDto.getProductionDate()); newBatch.setExpiryDate(detailDto.getExpiryDate()); newBatch.setPurchasePrice(detailDto.getUnitPrice()); newBatch.setSupplier(detailDto.getSupplier()); newBatch.setCurrentQuantity(detailDto.getQuantity()); newBatch.setStorageLocation(detailDto.getStorageLocation()); batchMapper.insert(newBatch); log.info(创建药品[ID:{}]新批次[{}]初始数量{}, detailDto.getMedicineId(), detailDto.getBatchNo(), detailDto.getQuantity()); } // 4.3 (可选) 更新药品基础信息表中的库存快照或统计信息 // 可以通过定时任务或视图来汇总避免实时更新影响性能。 } // 5. 发送审核通知如集成消息队列、邮件、站内信 // notificationService.sendAuditNotice(stockIn.getId(), stockIn.getHandlerId()); log.info(入库单[{}]创建成功等待审核。, inOrderNo); return true; } }4.2 智能库存预警服务实现预警不应该在每次查询时都进行全表扫描而应通过定时任务或数据库事件触发。// 文件路径src/main/java/com/pharmacy/service/impl/InventoryWarningServiceImpl.java Service Slf4j public class InventoryWarningServiceImpl implements InventoryWarningService { Autowired private MedicineBatchMapper batchMapper; Autowired private MedicineMapper medicineMapper; /** * 定时检查库存预警可被Spring Scheduled或Quartz调用 */ Scheduled(cron 0 0 8 * * ?) // 每天上午8点执行 Override public void checkStockWarning() { log.info(开始执行每日库存预警检查...); // 1. 检查库存不足当前总库存 最低预警线 ListMapString, Object lowStockList batchMapper.selectLowStockMedicine(); if (!lowStockList.isEmpty()) { log.warn(发现库存不足药品{}, lowStockList); // 调用通知服务发送预警给采购员或管理员 // warningNoticeService.sendLowStockWarning(lowStockList); } // 2. 检查库存积压当前总库存 最高预警线 ListMapString, Object overStockList batchMapper.selectOverStockMedicine(); if (!overStockList.isEmpty()) { log.warn(发现库存积压药品{}, overStockList); // warningNoticeService.sendOverStockWarning(overStockList); } // 3. 检查近效期药品例如有效期在30天内 LocalDate warningDate LocalDate.now().plusDays(30); ListMedicineBatch expiringBatches batchMapper.selectList( new LambdaQueryWrapperMedicineBatch() .lt(MedicineBatch::getExpiryDate, warningDate) .gt(MedicineBatch::getCurrentQuantity, 0) // 只查有库存的 .orderByAsc(MedicineBatch::getExpiryDate) // 按效期升序 ); if (!expiringBatches.isEmpty()) { log.warn(发现近效期药品批次{}, expiringBatches); // warningNoticeService.sendExpiringWarning(expiringBatches); } log.info(库存预警检查完成。); } /** * 实时查询某个药品的库存状态用于前端实时显示 */ Override public MedicineStockVO getMedicineStockStatus(Long medicineId) { MedicineStockVO vo new MedicineStockVO(); Medicine medicine medicineMapper.selectById(medicineId); if (medicine null) { throw new BusinessException(药品不存在); } BeanUtils.copyProperties(medicine, vo); // 计算该药品所有批次的总库存、可用库存、近效期数量 ListMedicineBatch batches batchMapper.selectList( new LambdaQueryWrapperMedicineBatch().eq(MedicineBatch::getMedicineId, medicineId) ); int totalStock batches.stream().mapToInt(MedicineBatch::getCurrentQuantity).sum(); int availableStock batches.stream().mapToInt(MedicineBatch::getAvailableQuantity).sum(); long expiringCount batches.stream() .filter(b - b.getExpiryDate().isBefore(LocalDate.now().plusDays(30))) .count(); vo.setTotalStock(totalStock); vo.setAvailableStock(availableStock); vo.setExpiringBatchCount((int) expiringCount); // 判断预警状态 if (totalStock medicine.getStockWarningMin()) { vo.setWarningType(库存不足); vo.setWarningLevel(高危); } else if (totalStock medicine.getStockWarningMax()) { vo.setWarningType(库存积压); vo.setWarningLevel(中危); } else if (expiringCount 0) { vo.setWarningType(近效期); vo.setWarningLevel(低危); } else { vo.setWarningType(正常); vo.setWarningLevel(无); } return vo; } }对应的MyBatis-Plus Mapper接口和XML中需要定义自定义查询// MedicineBatchMapper.java public interface MedicineBatchMapper extends BaseMapperMedicineBatch { // 查询库存低于预警线的药品聚合查询 Select(SELECT m.id, m.name, m.code, m.stock_warning_min, SUM(mb.current_quantity) as total_stock FROM medicine m LEFT JOIN medicine_batch mb ON m.id mb.medicine_id WHERE m.status 1 GROUP BY m.id, m.name, m.code, m.stock_warning_min HAVING total_stock m.stock_warning_min OR total_stock IS NULL) ListMapString, Object selectLowStockMedicine(); }5. 前端界面实现与交互优化前端使用Vue3 Element Plus Axios构建。我们以实现药品库存查询与预警列表页面为例。5.1 药品库存查询页面 (MedicineStock.vue)template div classmedicine-stock-container el-card template #header div classcard-header span药品库存查询/span el-button typeprimary clickhandleExport导出数据/el-button /div /template !-- 查询条件 -- el-form :modelqueryParams inline el-form-item label药品编码/名称 el-input v-modelqueryParams.keyword placeholder请输入编码或名称 clearable keyup.enterhandleQuery / /el-form-item el-form-item label库存状态 el-select v-modelqueryParams.warningType placeholder请选择 clearable el-option label全部 value / el-option label库存不足 valueLOW / el-option label库存积压 valueOVER / el-option label近效期 valueEXPIRE / el-option label正常 valueNORMAL / /el-select /el-form-item el-form-item el-button typeprimary clickhandleQuery查询/el-button el-button clickresetQuery重置/el-button /el-form-item /el-form !-- 库存列表 -- el-table :datastockList v-loadingloading stripe border el-table-column propcode label药品编码 width120 fixed / el-table-column propname label药品名称 min-width180 show-overflow-tooltip / el-table-column propspec label规格 width120 / el-table-column propmanufacturer label生产厂家 width150 show-overflow-tooltip / el-table-column proptotalStock label总库存 width100 aligncenter template #default{ row } span :class{ warning-text: row.warningType 库存不足 }{{ row.totalStock }}/span /template /el-table-column el-table-column propavailableStock label可用库存 width100 aligncenter / el-table-column propstockWarningMin label最低预警 width100 aligncenter / el-table-column propstockWarningMax label最高预警 width100 aligncenter / el-table-column propwarningType label预警状态 width100 aligncenter template #default{ row } el-tag :typegetWarningTagType(row.warningType) sizesmall {{ row.warningType }} /el-tag /template /el-table-column el-table-column propexpiringBatchCount label近效期批次 width110 aligncenter template #default{ row } el-button v-ifrow.expiringBatchCount 0 typetext clickviewExpiringBatch(row.id) span stylecolor: #e6a23c;{{ row.expiringBatchCount }} 批/span /el-button span v-else-/span /template /el-table-column el-table-column label操作 width180 fixedright template #default{ row } el-button typetext sizesmall clickviewDetail(row.id)库存明细/el-button el-button typetext sizesmall clickhandleAdjust(row.id)库存调整/el-button el-button typetext sizesmall clickhandleWarningSet(row)预警设置/el-button /template /el-table-column /el-table !-- 分页 -- div classpagination-container el-pagination v-model:current-pagequeryParams.pageNum v-model:page-sizequeryParams.pageSize :totaltotal :page-sizes[10, 20, 50, 100] layouttotal, sizes, prev, pager, next, jumper size-changehandleSizeChange current-changehandleCurrentChange / /div /el-card !-- 批次详情对话框 -- el-dialog v-modelbatchDialogVisible title批次库存明细 width80% BatchDetailTable :medicine-idcurrentMedicineId / /el-dialog /div /template script setup import { ref, reactive, onMounted } from vue import { ElMessage, ElMessageBox } from element-plus import { getMedicineStockList } from /api/inventory import BatchDetailTable from ./components/BatchDetailTable.vue // 查询参数 const queryParams reactive({ keyword: , warningType: , pageNum: 1, pageSize: 20 }) const loading ref(false) const stockList ref([]) const total ref(0) // 对话框控制 const batchDialogVisible ref(false) const currentMedicineId ref(null) // 获取库存列表 const fetchStockList async () { loading.value true try { const { data } await getMedicineStockList(queryParams) stockList.value data.list total.value data.total } catch (error) { ElMessage.error(获取库存列表失败 error.message) } finally { loading.value false } } // 查询 const handleQuery () { queryParams.pageNum 1 fetchStockList() } // 重置 const resetQuery () { Object.assign(queryParams, { keyword: , warningType: , pageNum: 1, pageSize: 20 }) fetchStockList() } // 分页事件 const handleSizeChange (val) { queryParams.pageSize val fetchStockList() } const handleCurrentChange (val) { queryParams.pageNum val fetchStockList() } // 预警标签类型 const getWarningTagType (type) { const map { 库存不足: danger, 库存积压: warning, 近效期: info, 正常: success } return map[type] || } // 查看近效期批次 const viewExpiringBatch (medicineId) { currentMedicineId.value medicineId batchDialogVisible.value true } // 导出数据 const handleExport async () { try { loading.value true // 调用导出API这里假设后端返回文件流 const response await getMedicineStockExport(queryParams) // 创建Blob对象并下载 const blob new Blob([response.data], { type: application/vnd.ms-excel }) const url window.URL.createObjectURL(blob) const link document.createElement(a) link.href url link.setAttribute(download, 药品库存_${new Date().getTime()}.xlsx) document.body.appendChild(link) link.click() document.body.removeChild(link) window.URL.revokeObjectURL(url) ElMessage.success(导出成功) } catch (error) { ElMessage.error(导出失败 error.message) } finally { loading.value false } } onMounted(() { fetchStockList() }) /script style scoped .warning-text { color: #f56c6c; font-weight: bold; } .pagination-container { margin-top: 20px; display: flex; justify-content: flex-end; } .card-header { display: flex; justify-content: space-between; align-items: center; } /style6. 系统部署与运维建议一个完整的项目必须考虑部署和上线后的运维。6.1 后端部署以Linux服务器为例环境准备安装JDK 11、MySQL 8.0、Redis用于缓存和分布式锁。数据库初始化执行提供的SQL脚本创建数据库和表结构。应用打包在项目根目录执行mvn clean package -DskipTests生成target/pharmacy-inventory-backend-0.0.1-SNAPSHOT.jar。配置文件准备application-prod.yml配置生产环境的数据库连接、Redis、文件路径等。# application-prod.yml spring: datasource: url: jdbc:mysql://localhost:3306/pharmacy_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: your_prod_username password: your_prod_password redis: host: localhost port: 6379 password: your_redis_password logging: file: name: /var/log/pharmacy/app.log level: com.pharmacy: DEBUG启动应用nohup java -jar pharmacy-inventory-backend-0.0.1-SNAPSHOT.jar --spring.profiles.activeprod app.log 21 使用Nginx反向代理可选用于负载均衡和静态资源server { listen 80; server_name your-domain.com; location /api/ { proxy_pass http://localhost:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location / { root /path/to/your/frontend/dist; try_files $uri $uri/ /index.html; } }6.2 前端部署构建生产版本在前端项目目录执行npm run build生成dist文件夹。部署静态资源将dist文件夹内的所有文件上传到Web服务器如Nginx的根目录或使用对象存储服务如阿里云OSS。6.3 运维监控与日志日志确保应用日志正确输出到文件并定期归档。使用logback或log4j2配置日志级别和滚动策略。健康检查Spring Boot Actuator提供了/actuator/health端点可用于监控应用状态。数据库备份设置MySQL的定时备份任务如使用mysqldump或xtrabackup。性能监控考虑集成Prometheus和Grafana监控JVM、数据库连接池、接口响应时间等指标。7. 常见问题与排查思路在开发和部署过程中你可能会遇到以下典型问题问题现象可能原因排查思路与解决方案前端请求后端API出现CORS错误后端未配置跨域支持1. 在后端添加CrossOrigin注解或全局CORS配置类。2. 通过Nginx反向代理统一域名和端口。插入数据时batch_no重复报错违反了medicine_batch表的唯一约束uk_medicine_batch1. 检查入库逻辑同一药品同一批号是否已存在。2. 在插入前先查询存在则更新数量不存在则新增。库存数量出现负数并发出库时未加锁导致超卖1.使用数据库悲观锁SELECT ... FOR UPDATE。2.使用乐观锁在medicine_batch表增加version字段更新时带版本条件。3.使用分布式锁如Redis锁在扣减库存前先获取锁。查询大量库存数据时页面卡顿1. 未分页查询。2. 关联查询未加索引。3. 前端一次性渲染数据过多。1. 后端必须实现分页查询MyBatis-Plus的Page对象。2. 检查SQL执行计划为常用查询条件添加索引。3. 前端使用分页组件或考虑虚拟滚动。定时预警任务未执行1.Scheduled注解未启用。2. Cron表达式错误。3. 任务方法抛出异常。1. 在主类添加EnableScheduling。2. 检查Cron表达式是否正确。3. 查看应用日志捕获任务方法内的异常。文件上传失败或路径错误1. 上传目录不存在或无权写入。2. 配置文件路径错误。3. 前端未正确设置Content-Type。1. 确保服务器上目录存在且有写权限。2. 检查application.yml中file.upload-path配置。3. 前端使用FormData对象上传。8. 项目扩展与进阶优化方向完成基础功能后可以从以下方向进行深度优化让项目脱颖而出引入Redis缓存场景药品基础信息、字典数据如药品分类、入库类型访问频繁变化不频繁。实现使用Spring Cache抽象注解Cacheable、CacheEvict。将热点数据存入Redis大幅减少数据库压力。Service public class MedicineServiceImpl { Cacheable(value medicine, key #id) public Medicine getById(Long id) { return medicineMapper.selectById(id); } CacheEvict(value medicine, key #medicine.id) public boolean update(Medicine medicine) { return medicineMapper.updateById(medicine) 0; } }集成消息队列如RabbitMQ进行异步处理场景入库单审核通过后需要更新库存、记录日志、发送通知。这些操作可以异步化提升主流程响应速度。实现审核通过后向队列发送一个消息。消费者服务监听队列依次执行库存更新、日志记录、发送邮件/短信通知等操作。实现分布式事务场景在微服务架构下一个业务可能涉及多个服务的数据修改如扣减库存、生成财务凭证需要保证一致性。实现调研并引入Seata框架使用AT、TCC等模式来保证跨服务的事务。增加数据分析与报表功能场景管理层需要查看药品消耗趋势、供应商供货分析、库存周转率等。实现使用ECharts或AntV等图表库后端通过复杂的SQL聚合查询或使用Elasticsearch进行数据分析提供可视化报表。完善权限控制系统实现真正的RBAC引入Spring Security设计用户-角色-权限模型。权限可以细化到按钮级别如“审核入库单”、“删除药品”。数据权限实现基于部门或药房的数据隔离例如A药房的药师只能看到和管理A药房的库存。容器化部署使用Docker将后端应用、前端、MySQL、Redis等分别制作成Docker镜像。使用Docker Compose编写docker-compose.yml文件一键启动所有服务极大简化部署流程。考虑K8s如果项目规模大可以考虑使用Kubernetes进行容器编排实现自动扩缩容和高可用。通过以上步骤你不仅完成了一个可运行的“医院药房药品库存管理系统”更掌握了一套从需求分析、数据库设计、核心业务逻辑实现、前后端交互到系统部署、优化和扩展的完整项目开发方法论。这个项目完全可以作为一份高质量的毕业设计或课程设计作品其中体现的批次管理、库存锁定、预警机制、事务控制等优化思想也正是企业级应用开发所关注的核心。