基于SSM框架的智慧养老平台核心模块设计与源码解析
1. SSM框架在智慧养老平台中的技术选型优势SSMSpringSpringMVCMyBatis作为JavaEE领域的经典框架组合在构建智慧养老平台时展现出独特的优势。我在实际项目中发现Spring的IoC容器能有效管理药品管理、健康档案等复杂业务对象比如通过注解方式声明药品库存服务Service public class DrugInventoryService { Autowired private DrugMapper drugMapper; public void updateStock(String drugId, int quantity) { // 药品库存更新逻辑 } }SpringMVC的拦截器特别适合处理养老平台的权限控制需求。我们曾实现过一个护工操作日志拦截器public class NurseOperationInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { // 验证护工操作权限 String nurseId request.getHeader(nurse-id); return permissionService.checkNursePermission(nurseId); } }MyBatis的动态SQL功能在处理老年人健康数据查询时尤为实用。比如这个多条件健康档案查询select idselectHealthRecords resultTypeHealthRecord SELECT * FROM health_record where if testelderId ! null AND elder_id #{elderId} /if if teststartDate ! null AND check_date #{startDate} /if if testendDate ! null AND check_date #{endDate} /if /where ORDER BY check_date DESC /select数据库设计方面MySQL的JSON类型字段可以灵活存储老年人健康指标CREATE TABLE health_record ( id BIGINT PRIMARY KEY, elder_id BIGINT NOT NULL, check_date DATETIME NOT NULL, indicators JSON COMMENT 血压/血糖等指标, nurse_id BIGINT COMMENT 记录护工 );2. 药品管理模块的精细化设计药品管理是养老平台的核心模块我们采用库存中心药品档案的双层架构。在数据库设计中特别注意到了药品批次管理CREATE TABLE drug_info ( id VARCHAR(20) PRIMARY KEY, name VARCHAR(100) NOT NULL, spec VARCHAR(50) COMMENT 规格, unit VARCHAR(10) COMMENT 单位, stock INT DEFAULT 0, min_stock INT COMMENT 最低库存预警 ); CREATE TABLE drug_batch ( batch_id VARCHAR(30) PRIMARY KEY, drug_id VARCHAR(20) NOT NULL, quantity INT NOT NULL, prod_date DATE, expire_date DATE, supplier VARCHAR(100), FOREIGN KEY (drug_id) REFERENCES drug_info(id) );入库操作我们实现了事务处理确保库存数据一致性Transactional public void drugInStock(DrugBatch batch) { // 1. 新增批次记录 batchMapper.insert(batch); // 2. 更新总库存 drugMapper.updateStock(batch.getDrugId(), batch.getQuantity()); // 3. 记录操作日志 operationLogService.logDrugOperation( IN, batch.getDrugId(), batch.getQuantity()); }前端采用Element UI实现药品库存看板template el-table :datadrugList stylewidth: 100% el-table-column propname label药品名称 / el-table-column propstock label库存 template #default{row} el-tag :typerow.stock row.minStock ? danger : success {{ row.stock }} {{ row.unit }} /el-tag /template /el-table-column /el-table /template3. 入住申请流程的状态机实现老年人入住流程涉及多个状态转换我们采用状态机模式设计public enum AdmissionState { PENDING(待审核), APPROVED(已批准), REJECTED(已拒绝), COMPLETED(已完成); private String desc; // constructor getter } Service public class AdmissionService { Autowired private StateMachineAdmissionState, AdmissionEvent stateMachine; public void processApplication(Long applicationId, AdmissionEvent event) { stateMachine.sendEvent(event); } }数据库表设计包含完整的审核轨迹CREATE TABLE admission_application ( id BIGINT PRIMARY KEY, elder_id BIGINT NOT NULL, apply_time DATETIME NOT NULL, current_state VARCHAR(20) NOT NULL, room_type VARCHAR(50) COMMENT 申请房型 ); CREATE TABLE admission_audit_log ( id BIGINT PRIMARY KEY, application_id BIGINT NOT NULL, from_state VARCHAR(20), to_state VARCHAR(20) NOT NULL, operator_id BIGINT, operate_time DATETIME NOT NULL, remark VARCHAR(200), FOREIGN KEY (application_id) REFERENCES admission_application(id) );前端采用流程图展示申请进度template el-steps :activecurrentStep finish-statussuccess el-step title申请提交 / el-step title资料审核 / el-step title健康评估 / el-step title房间分配 / el-step title入住完成 / /el-steps /template4. 健康档案模块的数据可视化健康档案模块采用Spring BootECharts实现数据可视化。后端接口设计RestController RequestMapping(/health) public class HealthController { GetMapping(/stats/{elderId}) public HealthStats getHealthStats( PathVariable Long elderId, RequestParam(required false) String period) { // 获取健康统计数据 } }MySQL中存储的血压记录表示例CREATE TABLE blood_pressure ( id BIGINT PRIMARY KEY, elder_id BIGINT NOT NULL, measure_time DATETIME NOT NULL, systolic INT COMMENT 收缩压, diastolic INT COMMENT 舒张压, heart_rate INT COMMENT 心率, measure_by BIGINT COMMENT 测量人员 );前端使用ECharts绘制趋势图// 血压趋势图配置 const option { tooltip: { trigger: axis }, xAxis: { type: category, data: dates }, yAxis: { type: value }, series: [ { name: 收缩压, type: line, data: systolicData }, { name: 舒张压, type: line, data: diastolicData } ] };5. 系统安全与性能优化实践采用Spring Security实现基于角色的访问控制Configuration EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers(/admin/**).hasRole(ADMIN) .antMatchers(/nurse/**).hasAnyRole(NURSE, ADMIN) .anyRequest().authenticated() .and() .formLogin(); } }MyBatis二级缓存配置提升药品查询性能cache evictionLRU flushInterval60000 size512 readOnlytrue/前端采用JWT进行接口认证// 请求拦截器 axios.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers.Authorization Bearer ${token}; } return config; });数据库连接池配置application.ymlspring: datasource: hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 18000006. 典型业务场景源码解析以药品出库为例展示完整业务流程Transactional public DrugOutStockResult outStock(DrugOutStockRequest request) { // 1. 验证库存 Drug drug drugMapper.selectById(request.getDrugId()); if (drug.getStock() request.getQuantity()) { throw new BizException(库存不足); } // 2. 扣减库存乐观锁 int rows drugMapper.reduceStock( request.getDrugId(), request.getQuantity(), drug.getVersion()); if (rows 0) { throw new ConcurrentUpdateException(); } // 3. 记录出库单 DrugOutStock outStock new DrugOutStock(); BeanUtils.copyProperties(request, outStock); outStockMapper.insert(outStock); // 4. 记录操作日志 operationLogService.recordOutStock( request.getOperatorId(), request.getDrugId(), request.getQuantity()); return new DrugOutStockResult(outStock.getId()); }对应的MyBatis更新语句update idreduceStock UPDATE drug_info SET stock stock - #{quantity}, version version 1 WHERE id #{drugId} AND version #{version} /update前端采用Vue实现出库表单export default { data() { return { form: { drugId: , quantity: 1, receiver: , purpose: }, rules: { drugId: [{ required: true }], quantity: [{ type: number, min: 1 }] } } }, methods: { submitForm() { this.$refs.form.validate(valid { if (valid) { drugService.outStock(this.form) .then(response { this.$message.success(出库成功); }); } }); } } }7. 异常处理与日志监控方案我们设计了统一的异常处理机制ControllerAdvice public class GlobalExceptionHandler { ExceptionHandler(BizException.class) ResponseBody public ResultVoid handleBizException(BizException e) { return Result.error(e.getCode(), e.getMessage()); } ExceptionHandler(Exception.class) ResponseBody public ResultVoid handleException(Exception e) { log.error(系统异常, e); return Result.error(500, 系统繁忙); } }操作日志的AOP实现Aspect Component public class OperationLogAspect { Autowired private OperationLogService logService; Around(annotation(logAnnotation)) public Object around(ProceedingJoinPoint joinPoint, OperationLog logAnnotation) throws Throwable { long start System.currentTimeMillis(); try { Object result joinPoint.proceed(); logService.recordLog( getOperator(), logAnnotation.value(), System.currentTimeMillis() - start, true); return result; } catch (Exception e) { logService.recordLog( getOperator(), logAnnotation.value(), System.currentTimeMillis() - start, false); throw e; } } }使用Logback配置日志分级存储appender nameFILE-ERROR classch.qos.logback.core.rolling.RollingFileAppender filter classch.qos.logback.classic.filter.LevelFilter levelERROR/level onMatchACCEPT/onMatch onMismatchDENY/onMismatch /filter filelogs/error.log/file rollingPolicy classch.qos.logback.core.rolling.TimeBasedRollingPolicy fileNamePatternlogs/error.%d{yyyy-MM-dd}.log/fileNamePattern /rollingPolicy /appender8. 前后端分离架构实践采用Swagger实现API文档自动化Configuration EnableSwagger2 public class SwaggerConfig { Bean public Docket api() { return new Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.basePackage(com.eldercare)) .paths(PathSelectors.any()) .build(); } }前端Axios请求封装const service axios.create({ baseURL: process.env.VUE_APP_BASE_API, timeout: 10000 }); service.interceptors.response.use( response { const res response.data; if (res.code ! 200) { Message.error(res.message || Error); return Promise.reject(new Error(res.message || Error)); } return res; }, error { Message.error(error.message); return Promise.reject(error); } );Vuex状态管理示例药品模块const drugStore { state: { drugList: [] }, mutations: { SET_DRUG_LIST(state, list) { state.drugList list; } }, actions: { async fetchDrugList({ commit }) { const { data } await getDrugList(); commit(SET_DRUG_LIST, data); } } };9. 数据库优化实战经验建立老年人健康档案的复合索引CREATE INDEX idx_elder_health ON health_record(elder_id, check_date);药品查询的SQL优化示例-- 优化前 SELECT * FROM drug_info WHERE name LIKE %感冒%; -- 优化后 SELECT * FROM drug_info WHERE name LIKE 感冒% ORDER BY stock DESC LIMIT 100;使用EXPLAIN分析慢查询EXPLAIN SELECT * FROM admission_application WHERE current_state PENDING AND apply_time 2023-01-01;MyBatis批量插入优化药品入库public void batchInsertDrugs(ListDrug drugs) { SqlSession session sqlSessionFactory.openSession(ExecutorType.BATCH); try { DrugMapper mapper session.getMapper(DrugMapper.class); for (Drug drug : drugs) { mapper.insert(drug); } session.commit(); } finally { session.close(); } }10. 部署与运维方案采用Docker Compose部署方案version: 3 services: mysql: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: elder_care ports: - 3306:3306 volumes: - ./mysql/data:/var/lib/mysql backend: build: ./backend ports: - 8080:8080 depends_on: - mysqlNginx配置负载均衡upstream backend { server backend1:8080; server backend2:8080; } server { listen 80; location / { proxy_pass http://backend; } }Spring Boot Actuator健康检查配置management: endpoints: web: exposure: include: health,info,metrics endpoint: health: show-details: always日志收集方案ELKfilebeat.inputs: - type: log paths: - /var/log/elder-care/*.log output.logstash: hosts: [logstash:5044]