患者健康档案功能旨在为患者提供一个便捷的健康信息录入和管理平台。该功能允许患者填写和更新个人健康档案,包括吸烟饮酒史、疾病诊断、过敏史和家族史等信息,为医生提供全面的患者健康背景。
根据项目数据库表设计规范(参见 docs/OLD/DevRule/07-数据库规范.md),创建患者健康档案表:
| 字段名 | 类型 | 描述 |
|---|---|---|
| id | BIGINT(20) | 主键ID,使用雪花算法(MyBatis-Plus ASSIGN_ID) |
| patient_user_id | BIGINT(20) | 患者用户ID,外键 |
| smoking_history | VARCHAR(50) | 吸烟史 (no-否, occasional-偶尔, frequent-经常) |
| drinking_history | VARCHAR(50) | 饮酒史 (no-否, occasional-偶尔, frequent-经常) |
| diabetes | TINYINT(1) | 糖尿病 (0-否, 1-是) |
| hypertension | TINYINT(1) | 高血压 (0-否, 1-是) |
| dyslipidemia | TINYINT(1) | 血脂异常 (0-否, 1-是) |
| coronary_heart_disease | TINYINT(1) | 冠心病 (0-否, 1-是) |
| cerebral_infarction | TINYINT(1) | 脑梗塞 (0-否, 1-是) |
| allergy_history | TEXT | 过敏史 |
| family_history | TEXT | 家族史 |
| version | INT(11) | 版本号(乐观锁) |
| create_user | BIGINT(20) | 创建者ID |
| create_time | DATETIME | 创建时间,默认值CURRENT_TIMESTAMP |
| update_user | BIGINT(20) | 更新者ID |
| update_time | DATETIME | 更新时间,默认值CURRENT_TIMESTAMP,更新时自动设置为当前时间 |
| remark | VARCHAR(255) | 备注 |
遵循的数据库规范要点:
BIGINT 主键并通过雪花算法生成(MyBatis-Plus ASSIGN_ID)。version、create_user、create_time、update_user、update_time)由 BaseEntity 与 CustomMetaObjectHandler 自动填充。V{version}__{description}.sql)。示例建表(参考):
CREATE TABLE `t_patient_health_record` (
`id` bigint(20) NOT NULL COMMENT '主键ID',
`patient_user_id` bigint(20) NOT NULL COMMENT '患者用户ID',
`smoking_history` varchar(50) DEFAULT NULL COMMENT '吸烟史 (no-否, occasional-偶尔, frequent-经常)',
`drinking_history` varchar(50) DEFAULT NULL COMMENT '饮酒史 (no-否, occasional-偶尔, frequent-经常)',
`diabetes` tinyint(1) DEFAULT '0' COMMENT '糖尿病 (0-否, 1-是)',
`hypertension` tinyint(1) DEFAULT '0' COMMENT '高血压 (0-否, 1-是)',
`dyslipidemia` tinyint(1) DEFAULT '0' COMMENT '血脂异常 (0-否, 1-是)',
`coronary_heart_disease` tinyint(1) DEFAULT '0' COMMENT '冠心病 (0-否, 1-是)',
`cerebral_infarction` tinyint(1) DEFAULT '0' COMMENT '脑梗塞 (0-否, 1-是)',
`allergy_history` text COMMENT '过敏史',
`family_history` text COMMENT '家族史',
`version` int(11) DEFAULT '0' COMMENT '版本号(乐观锁)',
`create_user` bigint(20) DEFAULT NULL COMMENT '创建者ID',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_user` bigint(20) DEFAULT NULL COMMENT '更新者ID',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`remark` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_patient_user_id` (`patient_user_id`),
KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='患者健康档案表';
备注:对外响应的 id 建议以字符串形式返回以避免前端 JS 精度丢失(参见 docs/OLD/DevRule/03-API设计规范.md 的长整型 ID 传输策略)。
package work.baiyun.chronicdiseaseapp.model.po;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Schema(description = "患者健康档案表")
@TableName("t_patient_health_record")
@Data
@EqualsAndHashCode(callSuper = false)
public class PatientHealthRecord extends BaseEntity {
@Schema(description = "患者用户ID")
@TableField("patient_user_id")
private Long patientUserId;
@Schema(description = "吸烟史")
@TableField("smoking_history")
private String smokingHistory;
@Schema(description = "饮酒史")
@TableField("drinking_history")
private String drinkingHistory;
@Schema(description = "糖尿病")
@TableField("diabetes")
private Boolean diabetes;
@Schema(description = "高血压")
@TableField("hypertension")
private Boolean hypertension;
@Schema(description = "血脂异常")
@TableField("dyslipidemia")
private Boolean dyslipidemia;
@Schema(description = "冠心病")
@TableField("coronary_heart_disease")
private Boolean coronaryHeartDisease;
@Schema(description = "脑梗塞")
@TableField("cerebral_infarction")
private Boolean cerebralInfarction;
@Schema(description = "过敏史")
@TableField("allergy_history")
private String allergyHistory;
@Schema(description = "家族史")
@TableField("family_history")
private String familyHistory;
}
// CreateOrUpdatePatientHealthRecordRequest.java
package work.baiyun.chronicdiseaseapp.model.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "创建或更新患者健康档案请求")
@Data
public class CreateOrUpdatePatientHealthRecordRequest {
@Schema(description = "吸烟史")
private String smokingHistory;
@Schema(description = "饮酒史")
private String drinkingHistory;
@Schema(description = "糖尿病")
private Boolean diabetes;
@Schema(description = "高血压")
private Boolean hypertension;
@Schema(description = "血脂异常")
private Boolean dyslipidemia;
@Schema(description = "冠心病")
private Boolean coronaryHeartDisease;
@Schema(description = "脑梗塞")
private Boolean cerebralInfarction;
@Schema(description = "过敏史")
private String allergyHistory;
@Schema(description = "家族史")
private String familyHistory;
}
// PatientHealthRecordResponse.java
package work.baiyun.chronicdiseaseapp.model.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "患者健康档案响应")
@Data
public class PatientHealthRecordResponse {
@Schema(description = "记录ID")
private String id;
@Schema(description = "患者用户ID")
private String patientUserId;
@Schema(description = "吸烟史")
private String smokingHistory;
@Schema(description = "饮酒史")
private String drinkingHistory;
@Schema(description = "糖尿病")
private Boolean diabetes;
@Schema(description = "高血压")
private Boolean hypertension;
@Schema(description = "血脂异常")
private Boolean dyslipidemia;
@Schema(description = "冠心病")
private Boolean coronaryHeartDisease;
@Schema(description = "脑梗塞")
private Boolean cerebralInfarction;
@Schema(description = "过敏史")
private String allergyHistory;
@Schema(description = "家族史")
private String familyHistory;
@Schema(description = "创建时间")
private LocalDateTime createTime;
@Schema(description = "更新时间")
private LocalDateTime updateTime;
@Schema(description = "患者昵称")
private String patientNickname;
}
// PatientHealthRecordMapper.java
package work.baiyun.chronicdiseaseapp.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import work.baiyun.chronicdiseaseapp.model.po.PatientHealthRecord;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface PatientHealthRecordMapper extends BaseMapper<PatientHealthRecord> {
}
// PatientHealthRecordService.java
package work.baiyun.chronicdiseaseapp.service;
import work.baiyun.chronicdiseaseapp.model.vo.PatientHealthRecordResponse;
import work.baiyun.chronicdiseaseapp.model.vo.CreateOrUpdatePatientHealthRecordRequest;
public interface PatientHealthRecordService {
/**
* 创建或更新患者健康档案
*/
void createOrUpdateHealthRecord(CreateOrUpdatePatientHealthRecordRequest request);
/**
* 获取当前用户的健康档案
*/
PatientHealthRecordResponse getCurrentUserHealthRecord();
/**
* 医生获取患者健康档案
*/
PatientHealthRecordResponse getPatientHealthRecord(Long patientUserId);
}
// PatientHealthRecordServiceImpl.java
package work.baiyun.chronicdiseaseapp.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import work.baiyun.chronicdiseaseapp.mapper.PatientHealthRecordMapper;
import work.baiyun.chronicdiseaseapp.mapper.UserInfoMapper;
import work.baiyun.chronicdiseaseapp.model.po.PatientHealthRecord;
import work.baiyun.chronicdiseaseapp.model.po.UserInfo;
import work.baiyun.chronicdiseaseapp.model.vo.PatientHealthRecordResponse;
import work.baiyun.chronicdiseaseapp.model.vo.CreateOrUpdatePatientHealthRecordRequest;
import work.baiyun.chronicdiseaseapp.service.PatientHealthRecordService;
import work.baiyun.chronicdiseaseapp.util.SecurityUtils;
@Service
public class PatientHealthRecordServiceImpl implements PatientHealthRecordService {
@Autowired
private PatientHealthRecordMapper patientHealthRecordMapper;
@Autowired
private UserInfoMapper userInfoMapper;
@Override
public void createOrUpdateHealthRecord(CreateOrUpdatePatientHealthRecordRequest request) {
Long userId = SecurityUtils.getCurrentUserId();
// 检查是否已有记录
LambdaQueryWrapper<PatientHealthRecord> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(PatientHealthRecord::getPatientUserId, userId);
PatientHealthRecord existingRecord = patientHealthRecordMapper.selectOne(wrapper);
if (existingRecord != null) {
// 更新现有记录
BeanUtils.copyProperties(request, existingRecord);
patientHealthRecordMapper.updateById(existingRecord);
} else {
// 创建新记录
PatientHealthRecord newRecord = new PatientHealthRecord();
BeanUtils.copyProperties(request, newRecord);
newRecord.setPatientUserId(userId);
patientHealthRecordMapper.insert(newRecord);
}
}
@Override
public PatientHealthRecordResponse getCurrentUserHealthRecord() {
Long userId = SecurityUtils.getCurrentUserId();
return getHealthRecordByPatientId(userId);
}
@Override
public PatientHealthRecordResponse getPatientHealthRecord(Long patientUserId) {
// 权限检查:只有医生和管理员可以查看其他患者的健康档案
work.baiyun.chronicdiseaseapp.enums.PermissionGroup role = SecurityUtils.getCurrentUserRole();
if (role != work.baiyun.chronicdiseaseapp.enums.PermissionGroup.DOCTOR &&
role != work.baiyun.chronicdiseaseapp.enums.PermissionGroup.SYS_ADMIN) {
throw new work.baiyun.chronicdiseaseapp.exception.CustomException(
work.baiyun.chronicdiseaseapp.enums.ErrorCode.DATA_ACCESS_DENIED.getCode(),
"无权查看其他患者的健康档案");
}
return getHealthRecordByPatientId(patientUserId);
}
private PatientHealthRecordResponse getHealthRecordByPatientId(Long patientUserId) {
LambdaQueryWrapper<PatientHealthRecord> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(PatientHealthRecord::getPatientUserId, patientUserId);
PatientHealthRecord record = patientHealthRecordMapper.selectOne(wrapper);
if (record == null) {
return null; // 或返回空对象
}
PatientHealthRecordResponse response = new PatientHealthRecordResponse();
BeanUtils.copyProperties(record, response);
response.setId(record.getId().toString());
// 获取患者昵称
UserInfo userInfo = userInfoMapper.selectById(patientUserId);
if (userInfo != null) {
response.setPatientNickname(userInfo.getNickname());
}
return response;
}
}
// PatientHealthRecordController.java
package work.baiyun.chronicdiseaseapp.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import work.baiyun.chronicdiseaseapp.common.R;
import work.baiyun.chronicdiseaseapp.model.vo.PatientHealthRecordResponse;
import work.baiyun.chronicdiseaseapp.model.vo.CreateOrUpdatePatientHealthRecordRequest;
import work.baiyun.chronicdiseaseapp.service.PatientHealthRecordService;
import work.baiyun.chronicdiseaseapp.enums.ErrorCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@RestController
@RequestMapping("/patient-health-record")
@Tag(name = "患者健康档案", description = "患者健康档案管理相关接口")
public class PatientHealthRecordController {
private static final Logger logger = LoggerFactory.getLogger(PatientHealthRecordController.class);
@Autowired
private PatientHealthRecordService patientHealthRecordService;
@Operation(summary = "创建或更新健康档案", description = "患者创建或更新自己的健康档案")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "健康档案保存成功",
content = @Content(mediaType = "application/json",
schema = @Schema(implementation = Void.class))),
@ApiResponse(responseCode = "500", description = "服务器内部错误",
content = @Content(mediaType = "application/json",
schema = @Schema(implementation = Void.class)))
})
@PostMapping(path = "/save", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public R<?> save(@RequestBody CreateOrUpdatePatientHealthRecordRequest req) {
try {
patientHealthRecordService.createOrUpdateHealthRecord(req);
return R.success(200, "健康档案保存成功");
} catch (Exception e) {
logger.error("save patient health record failed", e);
return R.fail(ErrorCode.SYSTEM_ERROR.getCode(), ErrorCode.SYSTEM_ERROR.getMessage());
}
}
@Operation(summary = "获取当前用户健康档案", description = "患者获取自己的健康档案")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "成功获取健康档案",
content = @Content(mediaType = "application/json",
schema = @Schema(implementation = PatientHealthRecordResponse.class))),
@ApiResponse(responseCode = "500", description = "服务器内部错误",
content = @Content(mediaType = "application/json",
schema = @Schema(implementation = Void.class)))
})
@GetMapping(path = "/my-record", produces = MediaType.APPLICATION_JSON_VALUE)
public R<?> getMyRecord() {
try {
PatientHealthRecordResponse response = patientHealthRecordService.getCurrentUserHealthRecord();
return R.success(200, "ok", response);
} catch (Exception e) {
logger.error("get my health record failed", e);
return R.fail(ErrorCode.SYSTEM_ERROR.getCode(), ErrorCode.SYSTEM_ERROR.getMessage());
}
}
@Operation(summary = "医生获取患者健康档案", description = "医生获取指定患者的健康档案(需有权限)")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "成功获取健康档案",
content = @Content(mediaType = "application/json",
schema = @Schema(implementation = PatientHealthRecordResponse.class))),
@ApiResponse(responseCode = "403", description = "无权限访问",
content = @Content(mediaType = "application/json",
schema = @Schema(implementation = Void.class)))
})
@GetMapping(path = "/patient/{patientUserId}", produces = MediaType.APPLICATION_JSON_VALUE)
public R<?> getPatientRecord(@PathVariable Long patientUserId) {
try {
PatientHealthRecordResponse response = patientHealthRecordService.getPatientHealthRecord(patientUserId);
return R.success(200, "ok", response);
} catch (Exception e) {
logger.error("get patient health record failed", e);
return R.fail(ErrorCode.SYSTEM_ERROR.getCode(), ErrorCode.SYSTEM_ERROR.getMessage());
}
}
}
在 ErrorCode 枚举中可能需要添加以下错误码:
// 在 ErrorCode.java 中添加
HEALTH_RECORD_ACCESS_DENIED(7000, "无权访问健康档案");