SpringBoot+Vue社区医院信息平台开发实践

SpringBoot+Vue社区医院信息平台开发实践

1. 项目背景与核心价值

社区医院信息化建设是当前医疗体系改革的重要环节。这个基于SpringBoot+Vue的社区医院信息平台,恰好解决了基层医疗机构在数字化转型过程中的痛点。我在实际医疗信息化项目实施中发现,许多社区医院仍在使用纸质档案或单机版管理系统,存在数据孤岛、流程低效等问题。

这套系统采用前后端分离架构,前端使用Vue.js构建响应式界面,后端基于SpringBoot提供RESTful API服务。相比传统JSP/Thymeleaf方案,这种架构能够更好地适应社区医院多终端访问的需求——无论是医生的办公电脑、护士站的平板,还是患者的手机端,都能获得一致的体验。

提示:选择SpringBoot+Vue技术栈时,要特别注意跨域问题和接口文档的规范性,这对医疗系统的多端协同至关重要。

2. 技术架构详解

2.1 后端技术选型

SpringBoot 2.7.x作为基础框架,配合以下核心组件:

  • 数据持久层:MyBatis-Plus 3.5.x(含PageHelper分页插件)
  • 安全认证:Spring Security + JWT
  • 接口文档:Swagger UI + Knife4j增强
  • 消息队列:ActiveMQ(用于预约提醒、报告通知等异步场景)
  • 缓存管理:Redis(高频访问的科室信息、医生排班等)
// 典型Controller示例 @RestController @RequestMapping("/api/patient") @Api(tags = "患者管理") public class PatientController { @Autowired private PatientService patientService; @GetMapping("/list") @ApiOperation("分页查询患者") public Result<PageInfo<Patient>> listPatients( @RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize) { return Result.success(patientService.getPatientList(pageNum, pageSize)); } }

2.2 前端技术方案

Vue 3.x组合式API开发,主要技术栈:

  • UI组件库:Element Plus(表格、表单等复杂组件)
  • 状态管理:Pinia(替代Vuex的轻量方案)
  • HTTP客户端:Axios(封装请求拦截器处理JWT刷新)
  • 图表库:ECharts(数据可视化看板)
  • 辅助工具:Vue-i18n(多语言)、Vue-router(路由守卫)
// 典型API调用示例 import { getPatientList } from '@/api/patient' const loadData = async () => { try { const res = await getPatientList({ pageNum: currentPage.value, pageSize: pageSize.value }) tableData.value = res.data.list total.value = res.data.total } catch (error) { ElMessage.error('获取患者列表失败') } }

3. 数据库设计与优化

3.1 核心表结构

-- 患者基本信息表 CREATE TABLE `patient` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', `card_no` varchar(32) NOT NULL COMMENT '就诊卡号', `name` varchar(50) NOT NULL COMMENT '姓名', `gender` tinyint DEFAULT '0' COMMENT '性别(0-未知 1-男 2-女)', `birth_date` date DEFAULT NULL COMMENT '出生日期', `id_card` varchar(18) DEFAULT NULL COMMENT '身份证号', `phone` varchar(20) DEFAULT NULL COMMENT '联系电话', `address` varchar(200) DEFAULT NULL COMMENT '住址', `allergy_history` text COMMENT '过敏史', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_card_no` (`card_no`), KEY `idx_id_card` (`id_card`), KEY `idx_phone` (`phone`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -- 医生排班表(关联科室、医生) CREATE TABLE `schedule` ( `id` bigint NOT NULL AUTO_INCREMENT, `dept_id` bigint NOT NULL COMMENT '科室ID', `doctor_id` bigint NOT NULL COMMENT '医生ID', `work_date` date NOT NULL COMMENT '排班日期', `time_slot` tinyint NOT NULL COMMENT '时段(1-上午 2-下午 3-晚上)', `total_count` int DEFAULT '0' COMMENT '总号源', `available_count` int DEFAULT '0' COMMENT '剩余号源', `status` tinyint DEFAULT '1' COMMENT '状态(0-停诊 1-正常)', PRIMARY KEY (`id`), UNIQUE KEY `uk_dept_doctor_time` (`dept_id`,`doctor_id`,`work_date`,`time_slot`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

3.2 性能优化实践

  1. 索引策略

    • 为高频查询条件建立组合索引(如科室+日期+时段的排班查询)
    • 使用覆盖索引减少回表(SELECT只包含索引字段)
  2. 慢SQL治理

    -- 优化前(全表扫描) SELECT * FROM medical_record WHERE patient_name LIKE '%张%'; -- 优化后(使用全文索引) ALTER TABLE medical_record ADD FULLTEXT INDEX ft_idx_content(patient_name,diagnosis); SELECT * FROM medical_record WHERE MATCH(patient_name) AGAINST('张*' IN BOOLEAN MODE);
  3. 分库分表

    • 就诊记录表按年度分表(medical_record_2023)
    • 药品库存表按科室分库

4. 关键业务模块实现

4.1 预约挂号流程

graph TD A[患者选择科室] --> B[展示可预约医生] B --> C{选择日期时段} C --> D[显示剩余号源] D --> E[填写就诊卡信息] E --> F[生成预约订单] F --> G[支付押金] G --> H[短信通知]

注意:实际开发中要处理高并发场景下的号源超卖问题,推荐使用Redis分布式锁:

public boolean makeAppointment(Long scheduleId) { String lockKey = "lock:schedule:" + scheduleId; String clientId = UUID.randomUUID().toString(); try { // 尝试获取锁 Boolean locked = redisTemplate.opsForValue() .setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS); if (Boolean.TRUE.equals(locked)) { // 查询剩余号源 Schedule schedule = scheduleMapper.selectById(scheduleId); if (schedule.getAvailableCount() > 0) { // 扣减库存 scheduleMapper.updateAvailableCount(scheduleId, -1); // 创建预约记录 createAppointmentRecord(schedule); return true; } } return false; } finally { // 释放锁 if (clientId.equals(redisTemplate.opsForValue().get(lockKey))) { redisTemplate.delete(lockKey); } } }

4.2 电子病历管理

采用富文本编辑器(WangEditor)与结构化数据并存方案:

  1. 病历模板使用XML定义字段约束
  2. 医生填写时自动校验关键指标(如血压范围)
  3. 最终保存为HTML格式(带版本控制)
// 病历组件示例 <template> <div class="record-editor"> <toolbar :editor="editorRef" /> <editor v-model="htmlContent" :defaultConfig="editorConfig" @onCreated="handleCreated" /> <div class="data-fields"> <el-form :model="structuredData"> <el-form-item label="体温(℃)" prop="temperature"> <el-input-number v-model="structuredData.temperature" :min="35" :max="42" :step="0.1"/> </el-form-item> <!-- 其他体征字段 --> </el-form> </div> </div> </template>

5. 系统安全设计

5.1 认证与授权

采用JWT+RefreshToken双令牌机制:

  1. AccessToken有效期2小时(存于内存)
  2. RefreshToken有效期7天(存Redis)
  3. 接口权限通过注解控制:
@PreAuthorize("hasRole('DOCTOR') || hasRole('CHIEF')") @PostMapping("/prescription") public Result createPrescription(@RequestBody PrescriptionDTO dto) { // 开处方逻辑 }

5.2 数据安全措施

  1. 敏感字段加密:

    // 身份证号加密存储 @ColumnTransformer( read = "AES_DECRYPT(id_card, '${aes.key}')", write = "AES_ENCRYPT(?, '${aes.key}')" ) private String idCard;
  2. 审计日志记录:

    CREATE TABLE `audit_log` ( `id` bigint NOT NULL AUTO_INCREMENT, `user_id` bigint DEFAULT NULL COMMENT '操作人', `operation` varchar(50) DEFAULT NULL COMMENT '操作类型', `method` varchar(200) DEFAULT NULL COMMENT '请求方法', `params` text COMMENT '请求参数', `ip` varchar(50) DEFAULT NULL COMMENT 'IP地址', `create_time` datetime DEFAULT NULL COMMENT '操作时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

6. 部署与运维方案

6.1 多环境配置

使用SpringBoot Profile管理不同环境配置:

# application-dev.yml server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/hospital_dev username: devuser password: dev123 # application-prod.yml server: port: 80 servlet: context-path: /hospital spring: datasource: url: jdbc:mysql://master.db:3306/hospital_prod?useSSL=false username: produser password: ${DB_PASSWORD} # 从环境变量读取

6.2 Docker容器化部署

后端Dockerfile示例:

FROM openjdk:11-jre VOLUME /tmp ARG JAR_FILE=target/*.jar COPY ${JAR_FILE} app.jar ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]

前端Nginx配置:

server { listen 80; server_name hospital.example.com; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } location /api { proxy_pass http://backend:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }

7. 毕设开发建议

  1. 需求聚焦:优先实现核心业务流程(挂号-就诊-缴费-取药)
  2. 文档规范
    • 使用Swagger生成API文档
    • 数据库设计文档包含ER图
    • 编写详细的部署手册
  3. 测试要点
    • 使用Postman进行接口测试
    • 使用JUnit编写单元测试(重点测试业务逻辑)
    • 进行压力测试(模拟挂号高峰场景)
// 示例测试类 @SpringBootTest public class AppointmentServiceTest { @Autowired private AppointmentService service; @Test @Transactional @Rollback public void testMakeAppointment() { AppointmentDTO dto = new AppointmentDTO(); dto.setPatientId(1L); dto.setScheduleId(101L); Result<AppointmentVO> result = service.makeAppointment(dto); assertTrue(result.isSuccess()); assertNotNull(result.getData().getAppointmentNo()); } }

8. 常见问题解决方案

  1. 跨域问题

    @Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("*") .allowedMethods("GET", "POST", "PUT", "DELETE") .allowedHeaders("*") .exposedHeaders("Authorization"); } }
  2. Vue路由刷新404

    location / { try_files $uri $uri/ /index.html; }
  3. MyBatis懒加载异常

    spring: jackson: serialization: fail-on-empty-beans: false
  4. 日期序列化问题

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private LocalDateTime createTime;

我在实际医疗系统开发中深刻体会到,稳定性比炫技更重要。曾经因为追求新技术栈导致线上事故,后来总结出三条原则:1) 核心功能先用成熟方案实现 2) 所有变更都要有回滚方案 3) 关键操作必须留痕。这个项目源码中特别注重了异常处理和日志记录,建议开发者不要轻易删除这些"防御性代码"。