1. 项目概述
这个基于Spring Boot的智能药箱系统是一个面向家庭和医疗机构的药品管理解决方案。作为一名有多年Java开发经验的工程师,我最近完成了这个系统的设计和实现工作。系统主要解决了药品分类混乱、过期药品管理困难以及用药咨询不便等实际问题。
系统采用B/S架构,前端使用Vue.js框架实现响应式布局,后端基于Spring Boot+MyBatis Plus技术栈开发,数据库选用MySQL。整个系统包含药品分类管理、药品信息查询、用药提醒、药品咨询等核心功能模块。特别值得一提的是,系统还集成了智能推荐算法,可以根据用户的用药记录和健康状况提供个性化的用药建议。
2. 系统架构设计
2.1 技术选型分析
在项目初期,我花了大量时间进行技术选型评估。最终确定的技术栈组合主要基于以下几个考虑:
-
Spring Boot框架:作为后端开发的首选,它提供了自动配置、快速启动的特性,大大简化了项目搭建和配置过程。特别是它的starter依赖机制,可以快速集成各种常用组件。
-
Vue.js前端框架:相比React和Angular,Vue的学习曲线更平缓,文档更友好,特别适合中小型项目。它的响应式数据绑定和组件化开发模式,让前端开发效率显著提升。
-
MyBatis Plus持久层:在原生MyBatis基础上增强了CRUD操作,提供了代码生成器、分页插件等实用功能,减少了大量样板代码的编写。
-
MySQL数据库:作为最流行的开源关系型数据库,它的稳定性、性能和社区支持都经过了长期验证,特别适合这种数据关系明确的管理系统。
技术选型心得:在实际开发中,我发现MyBatis Plus的Lambda查询构建器特别实用,它可以在编译期就检查SQL语句的正确性,避免了运行时才发现SQL错误的问题。
2.2 系统架构设计
系统采用经典的三层架构设计:
-
表现层:基于Vue.js实现,负责用户界面展示和交互逻辑处理。采用Element UI组件库保证UI风格统一。
-
业务逻辑层:Spring Boot应用,包含控制器(Controller)、服务(Service)和数据访问对象(DAO)。这一层实现了核心业务逻辑。
-
数据持久层:MyBatis Plus+MySQL组合,负责数据的持久化存储和高效访问。
架构图中特别设计了以下几个关键点:
- 前后端完全分离,通过RESTful API进行通信
- 使用JWT进行身份认证和授权
- 引入Redis缓存高频访问的药品数据
- 采用Swagger生成API文档,方便前后端协作
3. 核心功能实现
3.1 药品分类管理模块
药品分类是系统的核心功能之一。我设计了多级分类体系:
- 一级分类:按药品用途划分,如处方药、非处方药、保健品等
- 二级分类:按治疗领域划分,如心血管类、消化系统类等
- 三级分类:具体药品类型,如降压药、降糖药等
实现这个功能时,我采用了树形结构存储分类数据,数据库表设计如下:
sql复制CREATE TABLE `drug_category` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`parent_id` bigint(20) DEFAULT NULL COMMENT '父分类ID',
`name` varchar(50) NOT NULL COMMENT '分类名称',
`level` int(11) DEFAULT NULL COMMENT '分类层级',
`sort` int(11) DEFAULT '0' COMMENT '排序',
`icon` varchar(255) DEFAULT NULL COMMENT '图标',
`status` tinyint(1) DEFAULT '1' COMMENT '状态:0-禁用,1-启用',
PRIMARY KEY (`id`),
KEY `idx_parent_id` (`parent_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='药品分类表';
在前端实现上,使用Element UI的Tree组件展示分类树,并实现了拖拽排序、批量操作等功能。一个值得分享的技巧是:在渲染大型分类树时,采用懒加载方式,只有当用户展开父节点时才加载子节点数据,这显著提升了页面性能。
3.2 药品信息管理模块
药品信息管理包含以下核心功能:
- 药品基础信息维护:名称、规格、生产厂家、批准文号等
- 药品图片上传:支持多图上传和预览
- 药品条形码识别:集成ZXing库实现扫码录入
- 药品批次管理:记录不同批次的入库时间和有效期
在实现药品条形码识别功能时,我遇到了一个典型问题:不同厂商的药品条形码格式不一致。解决方案是设计一个通用的条形码解析器,通过正则表达式匹配不同格式的条形码,并提取有效信息。
药品表的关键字段设计:
sql复制CREATE TABLE `drug_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`category_id` bigint(20) NOT NULL COMMENT '分类ID',
`name` varchar(100) NOT NULL COMMENT '药品名称',
`spec` varchar(50) NOT NULL COMMENT '规格',
`manufacturer` varchar(100) DEFAULT NULL COMMENT '生产厂家',
`approval_number` varchar(50) DEFAULT NULL COMMENT '批准文号',
`barcode` varchar(50) DEFAULT NULL COMMENT '条形码',
`price` decimal(10,2) DEFAULT NULL COMMENT '参考价格',
`description` text COMMENT '药品说明',
`status` tinyint(1) DEFAULT '1' COMMENT '状态:0-下架,1-上架',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_barcode` (`barcode`),
KEY `idx_category` (`category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='药品信息表';
3.3 智能提醒功能实现
药品过期提醒是本系统的亮点功能之一。实现逻辑如下:
- 用户添加药品时,必须填写生产日期和有效期
- 系统每天凌晨执行定时任务,扫描即将过期的药品
- 通过站内消息、邮件或短信提醒用户
定时任务使用Spring的@Scheduled注解实现:
java复制@Component
public class DrugExpireReminder {
@Autowired
private DrugStockService drugStockService;
@Autowired
private MessageService messageService;
// 每天凌晨1点执行
@Scheduled(cron = "0 0 1 * * ?")
public void checkExpiringDrugs() {
// 查询7天内将过期的药品
List<DrugStock> expiringDrugs = drugStockService.findExpiringDrugs(7);
expiringDrugs.forEach(stock -> {
// 给药品所属用户发送提醒
messageService.sendExpireReminder(
stock.getUserId(),
stock.getDrug().getName(),
stock.getExpireDate()
);
});
}
}
在实际开发中,我发现直接使用@Scheduled有个缺点:当应用部署多个实例时,会导致任务重复执行。解决方案是引入分布式锁,或者使用专门的调度框架如Quartz。
4. 系统安全设计
4.1 认证与授权
系统采用JWT(JSON Web Token)进行身份认证,相比传统的Session方式有以下优势:
- 无状态,服务端不需要存储会话信息
- 可以包含自定义的声明(Claims)
- 天然支持跨域访问
JWT的生成和验证流程:
java复制public class JwtTokenProvider {
private String secretKey = "your-secret-key";
private long validityInMilliseconds = 3600000; // 1小时
public String createToken(String username, List<String> roles) {
Claims claims = Jwts.claims().setSubject(username);
claims.put("roles", roles);
Date now = new Date();
Date validity = new Date(now.getTime() + validityInMilliseconds);
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(validity)
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}
public boolean validateToken(String token) {
try {
Jws<Claims> claims = Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token);
return !claims.getBody().getExpiration().before(new Date());
} catch (JwtException | IllegalArgumentException e) {
throw new InvalidJwtAuthenticationException("Expired or invalid JWT token");
}
}
}
4.2 数据安全
在数据安全方面,我采取了以下措施:
- 所有密码使用BCrypt加密存储
- 敏感信息如身份证号在数据库中进行加密
- API接口全部采用HTTPS协议
- 实现防SQL注入、XSS攻击的过滤器
密码加密示例:
java复制public class PasswordEncoder {
public String encode(CharSequence rawPassword) {
return BCrypt.hashpw(rawPassword.toString(), BCrypt.gensalt());
}
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return BCrypt.checkpw(rawPassword.toString(), encodedPassword);
}
}
5. 系统测试与优化
5.1 测试策略
系统测试采用分层测试策略:
- 单元测试:使用JUnit+Mockito测试Service层
- 集成测试:使用SpringBootTest测试Controller层
- UI测试:使用Selenium进行端到端测试
- 性能测试:使用JMeter模拟高并发场景
一个典型的Service层单元测试示例:
java复制@ExtendWith(MockitoExtension.class)
class DrugServiceTest {
@Mock
private DrugRepository drugRepository;
@InjectMocks
private DrugServiceImpl drugService;
@Test
void shouldReturnDrugWhenValidBarcodeProvided() {
// 准备测试数据
String barcode = "6936983800011";
Drug expectedDrug = new Drug();
expectedDrug.setBarcode(barcode);
expectedDrug.setName("测试药品");
// 定义Mock行为
when(drugRepository.findByBarcode(barcode)).thenReturn(Optional.of(expectedDrug));
// 调用测试方法
Drug actualDrug = drugService.findByBarcode(barcode);
// 验证结果
assertNotNull(actualDrug);
assertEquals(expectedDrug.getName(), actualDrug.getName());
// 验证Mock交互
verify(drugRepository).findByBarcode(barcode);
}
}
5.2 性能优化
在性能优化方面,我主要做了以下工作:
-
数据库优化:
- 为常用查询字段添加索引
- 优化SQL语句,避免全表扫描
- 使用连接池管理数据库连接
-
缓存策略:
- 高频访问的药品数据缓存到Redis
- 使用Spring Cache抽象实现方法级缓存
- 设置合理的缓存过期时间
-
前端优化:
- 启用Gzip压缩
- 使用CDN分发静态资源
- 实现懒加载和分页查询
一个典型的缓存配置示例:
java复制@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30)) // 默认缓存30分钟
.disableCachingNullValues()
.serializeValuesWith(SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.transactionAware()
.build();
}
}
6. 部署与运维
6.1 系统部署
系统支持多种部署方式:
- 传统部署:将打包好的Spring Boot应用jar和前端静态资源部署到Tomcat服务器
- Docker部署:使用Docker容器化部署,便于环境一致性管理
- 云原生部署:支持Kubernetes集群部署,实现高可用和弹性伸缩
Docker部署的关键配置文件:
dockerfile复制# 后端Dockerfile
FROM openjdk:11-jre-slim
COPY target/medicine-box-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
# 前端Dockerfile
FROM nginx:alpine
COPY dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
6.2 监控与日志
为了便于系统运维,我集成了以下监控组件:
- Spring Boot Actuator:提供健康检查、指标监控等端点
- Prometheus+Grafana:实现系统指标的采集和可视化
- ELK栈:集中管理应用日志,便于问题排查
日志配置示例(Logback):
xml复制<configuration>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/medicine-box.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/medicine-box.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="FILE" />
</root>
</configuration>
7. 项目总结与展望
在开发这个智能药箱系统的过程中,我积累了不少实战经验。最大的收获是学会了如何在保证功能完整性的同时,兼顾系统的性能和用户体验。特别是在药品分类管理和智能提醒功能的实现上,通过不断优化算法和数据结构,最终达到了令人满意的效果。
几个特别值得分享的开发心得:
-
MyBatis Plus的使用技巧:在复杂查询场景下,可以结合Lambda表达式和QueryWrapper,既保证了类型安全,又能灵活构建动态SQL。
-
缓存策略的选择:不是所有数据都适合缓存,需要根据访问频率和数据变更频率来制定缓存策略。对于药品基础信息这类变更少、查询多的数据,缓存效果最好。
-
前端性能优化:对于药品列表这种可能数据量很大的页面,一定要实现分页或虚拟滚动,避免一次性加载过多数据导致页面卡顿。
未来可能的改进方向:
- 接入智能硬件,实现药品的自动识别和库存管理
- 增加药品相互作用检查功能,提升用药安全性
- 开发移动端APP,提供更便捷的用药提醒服务
- 引入机器学习算法,实现更精准的用药推荐
这个项目从技术选型到最终部署,涵盖了现代Java Web开发的完整流程。对于想要学习Spring Boot全栈开发的同学来说,具有很好的参考价值。特别是在药品管理这类业务系统的开发思路上,可以借鉴本项目的架构设计和实现方法。