在医药行业数字化转型的浪潮中,高效、精准的信息管理已成为提升机构运营效率和服务质量的核心环节。传统的医药管理多依赖手工台账和分散的Excel表格,不仅数据录入效率低下,更易在药品效期跟踪、库存盘点、供应商对账等关键环节出现人为差错,存在显著的管理风险与合规隐患。针对这一痛点,我们设计并实现了一套基于SSM(Spring + Spring MVC + MyBatis)架构的医药信息集中管理解决方案。该平台将药品信息、库存状态、供应商资料及客户数据进行一体化整合,通过标准化的流程设计与严谨的权限控制,为中小型药店、诊所及医药流通企业提供了全生命周期的数据管理支持。
系统采用经典的三层架构模式,确保了技术实现的清晰度与可维护性。Spring框架作为项目的核心控制容器,负责管理所有业务组件的生命周期与依赖关系,其声明式事务管理机制为数据库操作提供了原子性、一致性、隔离性与持久性(ACID)保障,尤其在处理药品入库、出库等涉及多表更新的业务时至关重要。Web层由Spring MVC模块承担,它通过前端控制器(DispatcherServlet)统一接收HTTP请求,并依据配置的路由策略分发给相应的控制器(Controller)进行处理,结合参数绑定与数据验证机制,有效简化了Web交互逻辑。持久层选用MyBatis框架,其优势在于将SQL语句与Java代码分离,通过XML或注解方式定义对象-关系映射(ORM),支持动态SQL组装,极大便利了复杂查询条件(如多字段药品检索、分页查询)的实现。前端界面基于JSP技术渲染,并集成jQuery与Bootstrap库,构建出响应式、交互友好的用户操作界面,结合Ajax技术实现无刷新数据交互,提升了用户的操作体验。

数据库设计是系统稳定性的基石。本系统共设计四张核心数据表,结构清晰,关系明确。药品信息表(medicine)作为主实体表,记录了药品的基本属性,包括名称、分类、规格、生产厂家、批准文号、库存数量、进价、售价以及关键的有效期字段。该表结构设计充分考虑了业务查询需求与数据完整性约束。
CREATE TABLE `medicine` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '药品ID',
`name` varchar(100) NOT NULL COMMENT '药品名称',
`type` varchar(50) DEFAULT NULL COMMENT '药品分类',
`specification` varchar(100) DEFAULT NULL COMMENT '规格',
`manufacturer` varchar(100) DEFAULT NULL COMMENT '生产厂家',
`approval_number` varchar(50) DEFAULT NULL COMMENT '批准文号',
`stock` int(11) NOT NULL DEFAULT '0' COMMENT '库存数量',
`purchase_price` decimal(10,2) NOT NULL COMMENT '进价',
`selling_price` decimal(10,2) NOT NULL COMMENT '售价',
`expiry_date` date NOT NULL COMMENT '有效期至',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_name_spec` (`name`,`specification`),
KEY `idx_type` (`type`),
KEY `idx_expiry` (`expiry_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='药品信息表';
此表设计的亮点在于:1)设置了联合唯一索引uk_name_spec于name和specification字段,防止重复录入同名同规格的药品;2)为type(分类)和expiry_date(有效期)字段建立了普通索引,显著提升了按分类查询和临近效期药品预警查询的性能;3)使用DECIMAL(10,2)类型存储价格,确保财务计算的精确性;4)包含create_time和update_time两个时间戳字段,便于审计与数据追踪。
供应商表(supplier)的设计则侧重于业务关联与信息完整性。
CREATE TABLE `supplier` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '供应商ID',
`name` varchar(100) NOT NULL COMMENT '供应商名称',
`contact_person` varchar(50) DEFAULT NULL COMMENT '联系人',
`contact_phone` varchar(20) DEFAULT NULL COMMENT '联系电话',
`address` varchar(200) DEFAULT NULL COMMENT '地址',
`bank_account` varchar(50) DEFAULT NULL COMMENT '银行账号',
`status` tinyint(1) DEFAULT '1' COMMENT '状态(1:正常 0:禁用)',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='供应商表';
该表通过uk_name唯一索引确保供应商名称不重复,并设有status状态字段实现软删除或供应商停用逻辑,避免因物理删除导致的历史业务数据关联断裂。
用户与权限表(user)的设计支撑了系统的安全访问控制。
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(100) NOT NULL COMMENT '密码',
`real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名',
`role` enum('admin','user') NOT NULL DEFAULT 'user' COMMENT '角色',
`last_login_time` datetime DEFAULT NULL COMMENT '最后登录时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表';
其核心在于role字段使用ENUM类型明确限定用户角色为admin(管理员)或user(普通用户),为后续基于角色的权限控制(RBAC)奠定了基础。密码字段采用varchar(100)长度,为存储经过哈希加密(如BCrypt)后的密文预留了充足空间。

核心功能实现深度解析
药品信息全生命周期管理 药品信息管理是系统的核心功能,涵盖了增、删、改、查、条件筛选等全套操作。后端通过
MedicineController接收前端请求,并调用Service层完成业务逻辑。Controller层代码示例 (MedicineController.java):
@Controller @RequestMapping("/medicine") public class MedicineController { @Autowired private MedicineService medicineService; // 条件分页查询药品列表 @RequestMapping("/list") @ResponseBody public PageResult<Medicine> listMedicines(MedicineQuery query, @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer limit) { return medicineService.queryByPage(query, page, limit); } // 新增或更新药品信息 @RequestMapping("/saveOrUpdate") @ResponseBody public Result saveOrUpdate(@Valid Medicine medicine, BindingResult result) { if (result.hasErrors()) { return Result.error(result.getFieldError().getDefaultMessage()); } try { medicineService.saveOrUpdateMedicine(medicine); return Result.ok("操作成功"); } catch (Exception e) { return Result.error("操作失败: " + e.getMessage()); } } // 根据ID删除药品(逻辑删除或物理删除) @RequestMapping("/delete/{id}") @ResponseBody public Result deleteMedicine(@PathVariable Integer id) { try { medicineService.deleteMedicineById(id); return Result.ok("删除成功"); } catch (Exception e) { return Result.error("删除失败: " + e.getMessage()); } } }此控制器清晰地定义了API端点,使用
@Valid注解进行参数校验,并返回统一的JSON结果封装。Service层代码示例 (MedicineServiceImpl.java):
@Service @Transactional public class MedicineServiceImpl implements MedicineService { @Autowired private MedicineMapper medicineMapper; @Override public PageResult<Medicine> queryByPage(MedicineQuery query, Integer page, Integer limit) { PageHelper.startPage(page, limit); List<Medicine> medicines = medicineMapper.selectByCondition(query); PageInfo<Medicine> pageInfo = new PageInfo<>(medicines); return new PageResult<>(pageInfo.getTotal(), medicines); } @Override public void saveOrUpdateMedicine(Medicine medicine) { if (medicine.getId() == null) { // 新增:检查重复 Medicine exist = medicineMapper.selectByNameAndSpec(medicine.getName(), medicine.getSpecification()); if (exist != null) { throw new RuntimeException("已存在同名同规格的药品"); } medicineMapper.insert(medicine); } else { // 更新 medicineMapper.updateByPrimaryKey(medicine); } } }Service层使用
@Transactional注解确保方法内数据库操作的原子性。分页查询借助PageHelper插件简化实现。saveOrUpdateMedicine方法体现了业务规则,如在新增时检查药品是否已存在。MyBatis Mapper XML片段 (MedicineMapper.xml):
<select id="selectByCondition" resultType="Medicine" parameterType="MedicineQuery"> SELECT * FROM medicine <where> <if test="name != null and name != ''"> AND name LIKE CONCAT('%', #{name}, '%') </if> <if test="type != null and type != ''"> AND type = #{type} </if> <if test="manufacturer != null and manufacturer != ''"> AND manufacturer LIKE CONCAT('%', #{manufacturer}, '%') </if> <if test="minStock != null"> AND stock >= #{minStock} </if> <if test="maxStock != null"> AND stock <= #{maxStock} </if> <if test="expiryDateStart != null"> AND expiry_date >= #{expiryDateStart} </if> <if test="expiryDateEnd != null"> AND expiry_date <= #{expiryDateEnd} </if> </where> ORDER BY update_time DESC </select>此动态SQL语句根据查询条件对象(MedicineQuery)中字段是否为空,灵活组装WHERE子句,高效支持多条件组合检索。
上图展示了药品信息的列表浏览界面,支持按名称、分类、厂家等多条件查询和分页显示。
药品信息新增或编辑表单,包含数据验证,确保录入信息的准确性。库存管理与预警机制 库存管理直接关联企业的现金流与运营风险。系统通过实时监控
medicine表中的stock和expiry_date字段实现库存状态跟踪与预警。库存预警查询Service方法:
@Override public List<Medicine> getLowStockAndExpiryAlerts(Integer lowStockThreshold, Integer daysToExpire) { // 查询库存低于阈值或近期将过期的药品 return medicineMapper.selectAlerts(lowStockThreshold, LocalDate.now().plusDays(daysToExpire)); }对应的Mapper XML:
<select id="selectAlerts" resultType="Medicine"> SELECT * FROM medicine WHERE stock < #{lowStockThreshold} OR expiry_date <= #{alertDate} </select>此功能可在后台定时任务或管理员登录时触发,将预警信息展示在仪表盘,提醒及时补货或处理临期药品。
基于角色的权限控制(RBAC) 系统通过拦截器(Interceptor)实现访问控制。拦截器会校验用户登录状态和角色权限。
权限拦截器代码示例 (AuthInterceptor.java):
@Component public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); User user = (User) session.getAttribute("currentUser"); if (user == null) { response.sendRedirect(request.getContextPath() + "/login"); return false; } // 检查管理员权限(示例:只有admin可访问/user/management) if (request.getRequestURI().contains("/user/management") && !"admin".equals(user.getRole())) { response.sendError(HttpServletResponse.SC_FORBIDDEN, "权限不足"); return false; } return true; } }该拦截器在请求处理前执行,若用户未登录则跳转至登录页,若普通用户尝试访问管理员功能则返回403错误。
管理员专属的用户管理界面,可进行用户信息的增删改查和角色分配。
普通用户登录后,其功能可能受限,例如仅能查询而无法修改数据。数据报表与导出功能 为支持经营决策,系统提供了数据查询结果导出为Excel或PDF报表的功能。使用Apache POI或iText等库在后端生成文件,并通过HttpServletResponse输出流返回给前端下载。
报表导出Controller片段:
@RequestMapping("/exportExcel") public void exportMedicineExcel(MedicineQuery query, HttpServletResponse response) { List<Medicine> list = medicineService.queryAll(query); // 使用POI创建工作簿和工作表 HSSFWorkbook workbook = new HSSFWorkbook(); HSSFSheet sheet = workbook.createSheet("药品清单"); // ... 填充表头和数据行 // 设置响应头,触发浏览器下载 response.setContentType("application/vnd.ms-excel"); response.setHeader("Content-Disposition", "attachment;filename=medicines.xls"); workbook.write(response.getOutputStream()); workbook.close(); }
系统生成的报表预览,支持打印或导出为常用格式。
实体模型与业务逻辑
系统的实体类(如Medicine, User, Supplier)是业务对象的Java映射,它们通过MyBatis与数据库表关联。这些实体类不仅定义了数据结构,也承载了简单的业务逻辑验证(如通过JSR-303注解进行字段校验)。
public class Medicine {
private Integer id;
@NotBlank(message = "药品名称不能为空")
private String name;
private String type;
private String specification;
private String manufacturer;
private String approvalNumber;
@Min(value = 0, message = "库存不能为负数")
private Integer stock;
@DecimalMin(value = "0.0", message = "进价必须大于0")
private BigDecimal purchasePrice;
@DecimalMin(value = "0.0", message = "售价必须大于0")
private BigDecimal sellingPrice;
@Future(message = "有效期必须是将来的日期")
private Date expiryDate;
// ... getters and setters
}
未来优化方向与功能展望
引入Redis缓存:针对频繁查询且变化不频繁的数据(如药品分类字典、供应商列表),可引入Redis作为缓存层,将数据缓存至内存,显著减少数据库访问压力,提升系统响应速度。实现上,可在Service层方法中加入缓存注解(如
@Cacheable)。实现完整的进销存流水账:当前库存是结果状态,缺乏过程追溯。可新增入库单、出库单、库存流水等表结构,记录每一次库存变动的明细(如采购入库、销售出库、盘点调整),实现更精细的库存管理和成本核算。
集成条码或二维码扫描:为提升药品录入和盘点的效率,可开发移动端应用或改造Web端,支持通过摄像头扫描药品包装上的条码或二维码,自动识别并填充药品信息(需对接第三方药品信息数据库API)。
增加数据可视化大屏:利用ECharts等前端图表库,为管理员构建数据可视化仪表盘,动态展示销售额趋势、药品销量TOP10、库存周转率等关键经营指标,辅助决策。
增强系统安全性:除了目前的角色控制,可进一步引入接口限流(防止恶意请求)、操作日志审计(记录关键数据的增删改操作)、以及对密码进行更强的加密哈希(如BCrypt)并强制定期更换。
该医药信息管理平台通过SSM框架的稳健组合,构建了一个功能完备、性能可靠、易于扩展的管理系统。其清晰的架构分层、严谨的数据库设计以及围绕核心业务场景的功能实现,为医药机构的日常运营提供了强有力的数字化工具,有效推动了业务流程的标准化与效率提升。随着后续技术的迭代与功能的深化,平台的价值将进一步放大。