这个基于SpringBoot的城乡居民基本医疗信息管理系统是我在完成毕业设计时开发的一个实际项目。作为一个完整的医疗信息化解决方案,它旨在解决当前基层医疗系统中存在的信息孤岛、服务效率低下等问题。
系统采用SpringBoot作为基础框架,结合MySQL数据库,实现了从用户管理到医疗服务全流程的信息化覆盖。在实际开发过程中,我遇到了不少技术挑战,比如如何确保医疗数据的安全性、如何优化系统在高并发场景下的性能等。下面我会详细分享这个系统的设计思路和实现过程。
选择SpringBoot作为基础框架主要基于以下几个考虑:
数据库选用MySQL 8.0,主要因为:
系统采用经典的三层架构:
这种分层设计使得各层职责明确,便于维护和扩展。在实际开发中,我发现这种架构特别适合中小型项目的快速迭代。
用户权限是系统的安全基石,我采用了RBAC(基于角色的访问控制)模型:
java复制@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String username;
@Column(nullable = false)
private String password;
@ManyToMany(fetch = FetchType.EAGER)
private Set<Role> roles = new HashSet<>();
// 其他字段和方法...
}
权限控制通过Spring Security实现:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/doctor/**").hasRole("DOCTOR")
.antMatchers("/patient/**").hasRole("PATIENT")
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/")
.permitAll();
}
}
医疗数据的特点是敏感且结构复杂,我设计了以下核心表结构:
sql复制CREATE TABLE medical_record (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
patient_id BIGINT NOT NULL,
doctor_id BIGINT NOT NULL,
diagnosis TEXT NOT NULL,
treatment TEXT,
prescription TEXT,
record_date DATETIME NOT NULL,
FOREIGN KEY (patient_id) REFERENCES user(id),
FOREIGN KEY (doctor_id) REFERENCES user(id)
);
在实现数据访问时,我使用了Spring Data JPA的Repository模式:
java复制public interface MedicalRecordRepository extends JpaRepository<MedicalRecord, Long> {
List<MedicalRecord> findByPatientId(Long patientId);
@Query("SELECT mr FROM MedicalRecord mr WHERE mr.doctor.id = :doctorId AND mr.recordDate BETWEEN :start AND :end")
List<MedicalRecord> findByDoctorAndDateRange(
@Param("doctorId") Long doctorId,
@Param("start") LocalDateTime start,
@Param("end") LocalDateTime end);
}
医疗数据必须加密存储,我采用了AES对称加密:
java复制@Service
public class EncryptionService {
private static final String ALGORITHM = "AES";
private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
private SecretKey secretKey;
private IvParameterSpec ivParameterSpec;
public EncryptionService() {
// 实际项目中应从安全配置读取
String secret = "my-secret-key-123"; // 示例,实际应使用更安全的密钥
byte[] keyBytes = secret.getBytes(StandardCharsets.UTF_8);
secretKey = new SecretKeySpec(Arrays.copyOf(keyBytes, 16), ALGORITHM);
ivParameterSpec = new IvParameterSpec(Arrays.copyOfRange(keyBytes, 0, 16));
}
public String encrypt(String data) {
try {
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec);
byte[] encrypted = cipher.doFinal(data.getBytes());
return Base64.getEncoder().encodeToString(encrypted);
} catch (Exception e) {
throw new RuntimeException("加密失败", e);
}
}
// 解密方法...
}
所有API接口都采用了HTTPS协议,并通过JWT进行认证:
java复制@Component
public class JwtTokenProvider {
private String jwtSecret = "medical-secret-key"; // 应从配置读取
private int jwtExpirationInMs = 3600000; // 1小时
public String generateToken(Authentication authentication) {
UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
Date now = new Date();
Date expiryDate = new Date(now.getTime() + jwtExpirationInMs);
return Jwts.builder()
.setSubject(Long.toString(userPrincipal.getId()))
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
}
// 验证方法...
}
为提高系统响应速度,我实现了多级缓存:
java复制@Configuration
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(1000));
return cacheManager;
}
}
java复制@Service
public class HospitalCacheService {
private final RedisTemplate<String, Object> redisTemplate;
@Cacheable(value = "hospitals", key = "#id")
public Hospital getHospitalById(Long id) {
// 数据库查询逻辑
}
@CacheEvict(value = "hospitals", key = "#hospital.id")
public void updateHospital(Hospital hospital) {
// 更新逻辑
}
}
针对医疗系统常见的查询场景,我做了以下优化:
sql复制CREATE INDEX idx_patient_date ON medical_record(patient_id, record_date);
CREATE INDEX idx_doctor_date ON medical_record(doctor_id, record_date);
properties复制spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.max-lifetime=2000000
在实现预约挂号功能时,遇到了并发导致的超预约问题。解决方案是使用数据库乐观锁:
java复制@Entity
public class Appointment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Version
private Integer version;
// 其他字段...
}
@Service
@Transactional
public class AppointmentService {
public boolean makeAppointment(Long appointmentId) {
Appointment appointment = appointmentRepository.findById(appointmentId)
.orElseThrow(() -> new ResourceNotFoundException("预约不存在"));
if (appointment.getRemainingSlots() <= 0) {
return false;
}
appointment.setRemainingSlots(appointment.getRemainingSlots() - 1);
appointmentRepository.save(appointment);
return true;
}
}
当查询大量医疗记录时,系统响应变慢。我采用了分页查询和延迟加载策略:
java复制public interface MedicalRecordRepository extends JpaRepository<MedicalRecord, Long> {
@EntityGraph(attributePaths = {"patient", "doctor"})
Page<MedicalRecord> findByPatientId(Long patientId, Pageable pageable);
}
@RestController
@RequestMapping("/api/records")
public class MedicalRecordController {
@GetMapping
public ResponseEntity<Page<MedicalRecord>> getRecords(
@RequestParam Long patientId,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
Pageable pageable = PageRequest.of(page, size, Sort.by("recordDate").descending());
return ResponseEntity.ok(recordRepository.findByPatientId(patientId, pageable));
}
}
为简化部署流程,我采用了Docker容器化方案:
dockerfile复制FROM openjdk:11-jre-slim
VOLUME /tmp
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
配合Docker Compose编排MySQL和Redis服务:
yaml复制version: '3'
services:
app:
build: .
ports:
- "8080:8080"
depends_on:
- mysql
- redis
environment:
- SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/medical_db
- SPRING_REDIS_HOST=redis
mysql:
image: mysql:8.0
environment:
- MYSQL_ROOT_PASSWORD=root
- MYSQL_DATABASE=medical_db
volumes:
- mysql_data:/var/lib/mysql
redis:
image: redis:alpine
volumes:
mysql_data:
集成Spring Boot Actuator进行健康监控:
properties复制management.endpoints.web.exposure.include=health,info,metrics
management.endpoint.health.show-details=always
使用Logback记录结构化日志:
xml复制<configuration>
<appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
</appender>
<root level="INFO">
<appender-ref ref="JSON"/>
</root>
</configuration>
经过这个项目的开发,我对SpringBoot生态有了更深入的理解。以下几个经验值得分享:
领域模型设计要先行:在开始编码前,我花了大量时间设计领域模型,这为后续开发节省了很多时间。
测试驱动开发:虽然项目时间紧张,但我还是坚持为关键功能编写单元测试和集成测试,这大大减少了线上bug。
性能优化要有的放矢:不要过早优化,应该先通过压测找出真正的性能瓶颈。
文档的重要性:完善的API文档和部署手册让项目交接和演示变得非常顺利。
如果重做这个项目,我会在以下几个方面进行改进: