|
|
@@ -0,0 +1,667 @@
|
|
|
+# 患者提醒数据管理功能设计文档
|
|
|
+
|
|
|
+## 1. 功能概述
|
|
|
+
|
|
|
+患者提醒数据管理功能旨在为慢性病患者提供个性化的健康提醒服务,包括血压、血糖、心率等健康数据测量提醒以及用药提醒。每个患者只有一条提醒设置记录,包含所有提醒开关和时间点设置。系统将根据设置的时间推送提醒通知。
|
|
|
+
|
|
|
+## 2. 数据库设计
|
|
|
+
|
|
|
+### 2.1 患者提醒设置表 (t_patient_reminder)
|
|
|
+
|
|
|
+根据项目数据库表设计规范(参见 `docs/OLD/DevRule/07-数据库规范.md`),创建患者提醒设置表:
|
|
|
+
|
|
|
+| 字段名 | 类型 | 描述 |
|
|
|
+|--------|------|------|
|
|
|
+| id | BIGINT(20) | 主键ID,使用雪花算法(MyBatis-Plus `ASSIGN_ID`) |
|
|
|
+| patient_user_id | BIGINT(20) | 患者用户ID,每个患者只有一条记录 |
|
|
|
+| is_notification_enabled | TINYINT(1) | 是否启用消息通知总开关 (0-禁用, 1-启用) |
|
|
|
+| is_subscription_available | TINYINT(1) | 一次性订阅开关,服务器每发送消息后需重新申请用户授权 (0-需要重新授权, 1-已授权可发送) |
|
|
|
+| is_blood_pressure_enabled | TINYINT(1) | 测量血压提醒开关 (0-禁用, 1-启用) |
|
|
|
+| blood_pressure_times | TEXT | 测量血压的时间点(JSON格式存储,参考用药管理功能设计文档) |
|
|
|
+| is_blood_sugar_enabled | TINYINT(1) | 测量血糖提醒开关 (0-禁用, 1-启用) |
|
|
|
+| blood_sugar_times | TEXT | 测量血糖的时间点(JSON格式存储) |
|
|
|
+| is_heart_rate_enabled | TINYINT(1) | 测量心率提醒开关 (0-禁用, 1-启用) |
|
|
|
+| heart_rate_times | TEXT | 测量心率的时间点(JSON格式存储) |
|
|
|
+| is_medication_enabled | TINYINT(1) | 用药提醒开关 (0-禁用, 1-启用) |
|
|
|
+| 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` 自动填充。
|
|
|
+- 根据需要添加外键约束(注意性能影响)或在应用层校验关联数据完整性。
|
|
|
+- 数据迁移建议使用 Flyway/Liquibase(脚本命名遵循 `V{version}__{description}.sql`)。
|
|
|
+
|
|
|
+示例建表(参考):
|
|
|
+```sql
|
|
|
+CREATE TABLE `t_patient_reminder` (
|
|
|
+ `id` bigint(20) NOT NULL COMMENT '主键ID',
|
|
|
+ `patient_user_id` bigint(20) NOT NULL COMMENT '患者用户ID',
|
|
|
+ `is_notification_enabled` tinyint(1) DEFAULT '1' COMMENT '是否启用消息通知总开关 (0-禁用, 1-启用)',
|
|
|
+ `is_subscription_available` tinyint(1) DEFAULT '1' COMMENT '一次性订阅开关 (0-需要重新授权, 1-已授权可发送)',
|
|
|
+ `is_blood_pressure_enabled` tinyint(1) DEFAULT '1' COMMENT '测量血压提醒开关 (0-禁用, 1-启用)',
|
|
|
+ `blood_pressure_times` text COMMENT '测量血压的时间点(JSON格式存储)',
|
|
|
+ `is_blood_sugar_enabled` tinyint(1) DEFAULT '0' COMMENT '测量血糖提醒开关 (0-禁用, 1-启用)',
|
|
|
+ `blood_sugar_times` text COMMENT '测量血糖的时间点(JSON格式存储)',
|
|
|
+ `is_heart_rate_enabled` tinyint(1) DEFAULT '1' COMMENT '测量心率提醒开关 (0-禁用, 1-启用)',
|
|
|
+ `heart_rate_times` text COMMENT '测量心率的时间点(JSON格式存储)',
|
|
|
+ `is_medication_enabled` tinyint(1) DEFAULT '1' COMMENT '用药提醒开关 (0-禁用, 1-启用)',
|
|
|
+ `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`)
|
|
|
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='患者提醒设置表';
|
|
|
+```
|
|
|
+
|
|
|
+## 3. 枚举类型设计
|
|
|
+
|
|
|
+本功能设计中不使用枚举类型,所有提醒类型直接作为字段存储在患者提醒设置表中。
|
|
|
+ return code == null ? null : ReminderType.fromCode(code);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public ReminderType getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
|
|
|
+ String code = rs.getString(columnIndex);
|
|
|
+ return code == null ? null : ReminderType.fromCode(code);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public ReminderType getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
|
|
|
+ String code = cs.getString(columnIndex);
|
|
|
+ return code == null ? null : ReminderType.fromCode(code);
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+## 4. 实体类设计
|
|
|
+
|
|
|
+### 4.1 PO实体类
|
|
|
+
|
|
|
+#### 4.1.1 患者提醒设置实体类 (PatientReminder.java)
|
|
|
+
|
|
|
+```java
|
|
|
+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_reminder")
|
|
|
+@Data
|
|
|
+@EqualsAndHashCode(callSuper = false)
|
|
|
+public class PatientReminder extends BaseEntity {
|
|
|
+ @Schema(description = "患者用户ID")
|
|
|
+ @TableField("patient_user_id")
|
|
|
+ private Long patientUserId;
|
|
|
+
|
|
|
+ @Schema(description = "是否启用消息通知总开关 (0-禁用, 1-启用)")
|
|
|
+ @TableField("is_notification_enabled")
|
|
|
+ private Boolean notificationEnabled;
|
|
|
+
|
|
|
+ @Schema(description = "一次性订阅开关 (0-需要重新授权, 1-已授权可发送)")
|
|
|
+ @TableField("is_subscription_available")
|
|
|
+ private Boolean subscriptionAvailable;
|
|
|
+
|
|
|
+ @Schema(description = "测量血压提醒开关 (0-禁用, 1-启用)")
|
|
|
+ @TableField("is_blood_pressure_enabled")
|
|
|
+ private Boolean bloodPressureEnabled;
|
|
|
+
|
|
|
+ @Schema(description = "测量血压的时间点(JSON格式存储)")
|
|
|
+ @TableField("blood_pressure_times")
|
|
|
+ private String bloodPressureTimes;
|
|
|
+
|
|
|
+ @Schema(description = "测量血糖提醒开关 (0-禁用, 1-启用)")
|
|
|
+ @TableField("is_blood_sugar_enabled")
|
|
|
+ private Boolean bloodSugarEnabled;
|
|
|
+
|
|
|
+ @Schema(description = "测量血糖的时间点(JSON格式存储)")
|
|
|
+ @TableField("blood_sugar_times")
|
|
|
+ private String bloodSugarTimes;
|
|
|
+
|
|
|
+ @Schema(description = "测量心率提醒开关 (0-禁用, 1-启用)")
|
|
|
+ @TableField("is_heart_rate_enabled")
|
|
|
+ private Boolean heartRateEnabled;
|
|
|
+
|
|
|
+ @Schema(description = "测量心率的时间点(JSON格式存储)")
|
|
|
+ @TableField("heart_rate_times")
|
|
|
+ private String heartRateTimes;
|
|
|
+
|
|
|
+ @Schema(description = "用药提醒开关 (0-禁用, 1-启用)")
|
|
|
+ @TableField("is_medication_enabled")
|
|
|
+ private Boolean medicationEnabled;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 4.2 VO对象
|
|
|
+
|
|
|
+#### 4.2.1 请求对象
|
|
|
+
|
|
|
+```java
|
|
|
+// PatientReminderRequest.java
|
|
|
+package work.baiyun.chronicdiseaseapp.model.vo;
|
|
|
+
|
|
|
+import io.swagger.v3.oas.annotations.media.Schema;
|
|
|
+import lombok.Data;
|
|
|
+import java.util.List;
|
|
|
+
|
|
|
+@Schema(description = "患者提醒设置请求")
|
|
|
+@Data
|
|
|
+public class PatientReminderRequest {
|
|
|
+ @Schema(description = "是否启用消息通知总开关")
|
|
|
+ private Boolean notificationEnabled;
|
|
|
+
|
|
|
+ @Schema(description = "一次性订阅开关")
|
|
|
+ private Boolean subscriptionAvailable;
|
|
|
+
|
|
|
+ @Schema(description = "测量血压提醒开关")
|
|
|
+ private Boolean bloodPressureEnabled;
|
|
|
+
|
|
|
+ @Schema(description = "测量血压的时间点列表")
|
|
|
+ private List<String> bloodPressureTimes;
|
|
|
+
|
|
|
+ @Schema(description = "测量血糖提醒开关")
|
|
|
+ private Boolean bloodSugarEnabled;
|
|
|
+
|
|
|
+ @Schema(description = "测量血糖的时间点列表")
|
|
|
+ private List<String> bloodSugarTimes;
|
|
|
+
|
|
|
+ @Schema(description = "测量心率提醒开关")
|
|
|
+ private Boolean heartRateEnabled;
|
|
|
+
|
|
|
+ @Schema(description = "测量心率的时间点列表")
|
|
|
+ private List<String> heartRateTimes;
|
|
|
+
|
|
|
+ @Schema(description = "用药提醒开关")
|
|
|
+ private Boolean medicationEnabled;
|
|
|
+}
|
|
|
+
|
|
|
+// PatientReminderQueryRequest.java
|
|
|
+package work.baiyun.chronicdiseaseapp.model.vo;
|
|
|
+
|
|
|
+import io.swagger.v3.oas.annotations.media.Schema;
|
|
|
+import lombok.Data;
|
|
|
+import lombok.EqualsAndHashCode;
|
|
|
+
|
|
|
+@Schema(description = "患者提醒设置查询请求")
|
|
|
+@Data
|
|
|
+@EqualsAndHashCode(callSuper = true)
|
|
|
+public class PatientReminderQueryRequest extends BaseQueryRequest {
|
|
|
+ @Schema(description = "患者用户ID")
|
|
|
+ private Long patientUserId;
|
|
|
+
|
|
|
+ @Schema(description = "是否启用消息通知总开关")
|
|
|
+ private Boolean notificationEnabled;
|
|
|
+
|
|
|
+ @Schema(description = "测量血压提醒开关")
|
|
|
+ private Boolean bloodPressureEnabled;
|
|
|
+
|
|
|
+ @Schema(description = "测量血糖提醒开关")
|
|
|
+ private Boolean bloodSugarEnabled;
|
|
|
+
|
|
|
+ @Schema(description = "测量心率提醒开关")
|
|
|
+ private Boolean heartRateEnabled;
|
|
|
+
|
|
|
+ @Schema(description = "用药提醒开关")
|
|
|
+ private Boolean medicationEnabled;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+#### 4.2.2 响应对象
|
|
|
+
|
|
|
+```java
|
|
|
+// PatientReminderResponse.java
|
|
|
+package work.baiyun.chronicdiseaseapp.model.vo;
|
|
|
+
|
|
|
+import io.swagger.v3.oas.annotations.media.Schema;
|
|
|
+import lombok.Data;
|
|
|
+import java.util.List;
|
|
|
+
|
|
|
+@Schema(description = "患者提醒设置响应")
|
|
|
+@Data
|
|
|
+public class PatientReminderResponse {
|
|
|
+ @Schema(description = "记录ID")
|
|
|
+ private String id;
|
|
|
+
|
|
|
+ @Schema(description = "患者用户ID")
|
|
|
+ private String patientUserId;
|
|
|
+
|
|
|
+ @Schema(description = "是否启用消息通知总开关")
|
|
|
+ private Boolean notificationEnabled;
|
|
|
+
|
|
|
+ @Schema(description = "一次性订阅开关")
|
|
|
+ private Boolean subscriptionAvailable;
|
|
|
+
|
|
|
+ @Schema(description = "测量血压提醒开关")
|
|
|
+ private Boolean bloodPressureEnabled;
|
|
|
+
|
|
|
+ @Schema(description = "测量血压的时间点列表")
|
|
|
+ private List<String> bloodPressureTimes;
|
|
|
+
|
|
|
+ @Schema(description = "测量血糖提醒开关")
|
|
|
+ private Boolean bloodSugarEnabled;
|
|
|
+
|
|
|
+ @Schema(description = "测量血糖的时间点列表")
|
|
|
+ private List<String> bloodSugarTimes;
|
|
|
+
|
|
|
+ @Schema(description = "测量心率提醒开关")
|
|
|
+ private Boolean heartRateEnabled;
|
|
|
+
|
|
|
+ @Schema(description = "测量心率的时间点列表")
|
|
|
+ private List<String> heartRateTimes;
|
|
|
+
|
|
|
+ @Schema(description = "用药提醒开关")
|
|
|
+ private Boolean medicationEnabled;
|
|
|
+
|
|
|
+ @Schema(description = "创建时间")
|
|
|
+ private java.time.LocalDateTime createTime;
|
|
|
+}
|
|
|
+
|
|
|
+// PatientReminderOverviewResponse.java
|
|
|
+package work.baiyun.chronicdiseaseapp.model.vo;
|
|
|
+
|
|
|
+import io.swagger.v3.oas.annotations.media.Schema;
|
|
|
+import lombok.Data;
|
|
|
+
|
|
|
+@Schema(description = "患者提醒概览响应")
|
|
|
+@Data
|
|
|
+public class PatientReminderOverviewResponse {
|
|
|
+ @Schema(description = "患者提醒设置")
|
|
|
+ private PatientReminderResponse reminder;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+## 5. Mapper接口设计
|
|
|
+
|
|
|
+```java
|
|
|
+// PatientReminderMapper.java
|
|
|
+package work.baiyun.chronicdiseaseapp.mapper;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
|
|
+import work.baiyun.chronicdiseaseapp.model.po.PatientReminder;
|
|
|
+import org.apache.ibatis.annotations.Mapper;
|
|
|
+import org.apache.ibatis.annotations.Param;
|
|
|
+
|
|
|
+@Mapper
|
|
|
+public interface PatientReminderMapper extends BaseMapper<PatientReminder> {
|
|
|
+ PatientReminder selectByPatientUserId(@Param("patientUserId") Long patientUserId);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+## 6. Service层设计
|
|
|
+
|
|
|
+### 6.1 接口定义
|
|
|
+
|
|
|
+```java
|
|
|
+// PatientReminderService.java
|
|
|
+package work.baiyun.chronicdiseaseapp.service;
|
|
|
+
|
|
|
+import work.baiyun.chronicdiseaseapp.model.vo.PatientReminderOverviewResponse;
|
|
|
+import work.baiyun.chronicdiseaseapp.model.vo.CreatePatientReminderRequest;
|
|
|
+import work.baiyun.chronicdiseaseapp.model.vo.UpdatePatientReminderRequest;
|
|
|
+
|
|
|
+public interface PatientReminderService {
|
|
|
+ /**
|
|
|
+ * 获取患者提醒概览
|
|
|
+ */
|
|
|
+ PatientReminderOverviewResponse getReminderOverview();
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 保存患者提醒设置(创建或更新)
|
|
|
+ */
|
|
|
+ void savePatientReminder(CreatePatientReminderRequest request);
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 删除患者提醒设置
|
|
|
+ */
|
|
|
+ void deletePatientReminder(Long id);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 6.2 实现类
|
|
|
+
|
|
|
+```java
|
|
|
+// PatientReminderServiceImpl.java
|
|
|
+package work.baiyun.chronicdiseaseapp.service.impl;
|
|
|
+
|
|
|
+import com.alibaba.fastjson.JSON;
|
|
|
+import org.springframework.beans.BeanUtils;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+import work.baiyun.chronicdiseaseapp.mapper.PatientReminderMapper;
|
|
|
+import work.baiyun.chronicdiseaseapp.model.po.PatientReminder;
|
|
|
+import work.baiyun.chronicdiseaseapp.model.vo.PatientReminderOverviewResponse;
|
|
|
+import work.baiyun.chronicdiseaseapp.model.vo.PatientReminderResponse;
|
|
|
+import work.baiyun.chronicdiseaseapp.model.vo.CreatePatientReminderRequest;
|
|
|
+import work.baiyun.chronicdiseaseapp.service.PatientReminderService;
|
|
|
+import work.baiyun.chronicdiseaseapp.util.SecurityUtils;
|
|
|
+import java.util.List;
|
|
|
+
|
|
|
+@Service
|
|
|
+public class PatientReminderServiceImpl implements PatientReminderService {
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private PatientReminderMapper patientReminderMapper;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public PatientReminderOverviewResponse getReminderOverview() {
|
|
|
+ Long userId = SecurityUtils.getCurrentUserId();
|
|
|
+
|
|
|
+ PatientReminder patientReminder = patientReminderMapper.selectByPatientUserId(userId);
|
|
|
+
|
|
|
+ PatientReminderOverviewResponse response = new PatientReminderOverviewResponse();
|
|
|
+
|
|
|
+ if (patientReminder != null) {
|
|
|
+ PatientReminderResponse reminderResponse = new PatientReminderResponse();
|
|
|
+ BeanUtils.copyProperties(patientReminder, reminderResponse);
|
|
|
+ reminderResponse.setId(patientReminder.getId().toString());
|
|
|
+ reminderResponse.setBloodPressureTimes(JSON.parseArray(patientReminder.getBloodPressureTimes(), String.class));
|
|
|
+ reminderResponse.setBloodSugarTimes(JSON.parseArray(patientReminder.getBloodSugarTimes(), String.class));
|
|
|
+ reminderResponse.setHeartRateTimes(JSON.parseArray(patientReminder.getHeartRateTimes(), String.class));
|
|
|
+ response.setReminder(reminderResponse);
|
|
|
+ }
|
|
|
+
|
|
|
+ return response;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void savePatientReminder(CreatePatientReminderRequest request) {
|
|
|
+ Long userId = SecurityUtils.getCurrentUserId();
|
|
|
+
|
|
|
+ // 检查是否已存在记录
|
|
|
+ PatientReminder existing = patientReminderMapper.selectByPatientUserId(userId);
|
|
|
+
|
|
|
+ if (existing == null) {
|
|
|
+ // 创建患者提醒设置
|
|
|
+ PatientReminder patientReminder = new PatientReminder();
|
|
|
+ patientReminder.setPatientUserId(userId);
|
|
|
+ patientReminder.setNotificationEnabled(request.getNotificationEnabled() != null ? request.getNotificationEnabled() : true);
|
|
|
+ patientReminder.setSubscriptionAvailable(request.getSubscriptionAvailable() != null ? request.getSubscriptionAvailable() : true);
|
|
|
+ patientReminder.setBloodPressureEnabled(request.getBloodPressureEnabled() != null ? request.getBloodPressureEnabled() : true);
|
|
|
+ patientReminder.setBloodPressureTimes(JSON.toJSONString(request.getBloodPressureTimes()));
|
|
|
+ patientReminder.setBloodSugarEnabled(request.getBloodSugarEnabled() != null ? request.getBloodSugarEnabled() : false);
|
|
|
+ patientReminder.setBloodSugarTimes(JSON.toJSONString(request.getBloodSugarTimes()));
|
|
|
+ patientReminder.setHeartRateEnabled(request.getHeartRateEnabled() != null ? request.getHeartRateEnabled() : true);
|
|
|
+ patientReminder.setHeartRateTimes(JSON.toJSONString(request.getHeartRateTimes()));
|
|
|
+ patientReminder.setMedicationEnabled(request.getMedicationEnabled() != null ? request.getMedicationEnabled() : true);
|
|
|
+ patientReminderMapper.insert(patientReminder);
|
|
|
+ } else {
|
|
|
+ // 更新提醒设置
|
|
|
+ if (request.getNotificationEnabled() != null) {
|
|
|
+ existing.setNotificationEnabled(request.getNotificationEnabled());
|
|
|
+ }
|
|
|
+
|
|
|
+ if (request.getSubscriptionAvailable() != null) {
|
|
|
+ existing.setSubscriptionAvailable(request.getSubscriptionAvailable());
|
|
|
+ }
|
|
|
+
|
|
|
+ if (request.getBloodPressureEnabled() != null) {
|
|
|
+ existing.setBloodPressureEnabled(request.getBloodPressureEnabled());
|
|
|
+ }
|
|
|
+
|
|
|
+ if (request.getBloodPressureTimes() != null) {
|
|
|
+ existing.setBloodPressureTimes(JSON.toJSONString(request.getBloodPressureTimes()));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (request.getBloodSugarEnabled() != null) {
|
|
|
+ existing.setBloodSugarEnabled(request.getBloodSugarEnabled());
|
|
|
+ }
|
|
|
+
|
|
|
+ if (request.getBloodSugarTimes() != null) {
|
|
|
+ existing.setBloodSugarTimes(JSON.toJSONString(request.getBloodSugarTimes()));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (request.getHeartRateEnabled() != null) {
|
|
|
+ existing.setHeartRateEnabled(request.getHeartRateEnabled());
|
|
|
+ }
|
|
|
+
|
|
|
+ if (request.getHeartRateTimes() != null) {
|
|
|
+ existing.setHeartRateTimes(JSON.toJSONString(request.getHeartRateTimes()));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (request.getMedicationEnabled() != null) {
|
|
|
+ existing.setMedicationEnabled(request.getMedicationEnabled());
|
|
|
+ }
|
|
|
+
|
|
|
+ patientReminderMapper.updateById(existing);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void deletePatientReminder(Long id) {
|
|
|
+ Long userId = SecurityUtils.getCurrentUserId();
|
|
|
+
|
|
|
+ // 检查权限
|
|
|
+ PatientReminder patientReminder = patientReminderMapper.selectById(id);
|
|
|
+ if (patientReminder == null) {
|
|
|
+ throw new work.baiyun.chronicdiseaseapp.exception.CustomException(
|
|
|
+ work.baiyun.chronicdiseaseapp.enums.ErrorCode.DATA_NOT_FOUND.getCode(),
|
|
|
+ "患者提醒设置不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!patientReminder.getPatientUserId().equals(userId)) {
|
|
|
+ throw new work.baiyun.chronicdiseaseapp.exception.CustomException(
|
|
|
+ work.baiyun.chronicdiseaseapp.enums.ErrorCode.DATA_ACCESS_DENIED.getCode(),
|
|
|
+ "无权操作该患者提醒设置");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 删除提醒设置
|
|
|
+ patientReminderMapper.deleteById(id);
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+## 7. Controller层设计
|
|
|
+
|
|
|
+```java
|
|
|
+// PatientReminderController.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.PatientReminderOverviewResponse;
|
|
|
+import work.baiyun.chronicdiseaseapp.model.vo.CreatePatientReminderRequest;
|
|
|
+import work.baiyun.chronicdiseaseapp.service.PatientReminderService;
|
|
|
+import work.baiyun.chronicdiseaseapp.enums.ErrorCode;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+
|
|
|
+@RestController
|
|
|
+@RequestMapping("/patient-reminder")
|
|
|
+@Tag(name = "患者提醒管理", description = "患者健康提醒与用药提醒相关接口")
|
|
|
+public class PatientReminderController {
|
|
|
+
|
|
|
+ private static final Logger logger = LoggerFactory.getLogger(PatientReminderController.class);
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private PatientReminderService patientReminderService;
|
|
|
+
|
|
|
+ @Operation(summary = "获取提醒概览", description = "获取患者的所有提醒设置概览")
|
|
|
+ @ApiResponses(value = {
|
|
|
+ @ApiResponse(responseCode = "200", description = "成功获取提醒概览",
|
|
|
+ content = @Content(mediaType = "application/json",
|
|
|
+ schema = @Schema(implementation = PatientReminderOverviewResponse.class))),
|
|
|
+ @ApiResponse(responseCode = "500", description = "服务器内部错误",
|
|
|
+ content = @Content(mediaType = "application/json",
|
|
|
+ schema = @Schema(implementation = Void.class)))
|
|
|
+ })
|
|
|
+ @GetMapping(path = "/overview", produces = MediaType.APPLICATION_JSON_VALUE)
|
|
|
+ public R<?> getOverview() {
|
|
|
+ try {
|
|
|
+ PatientReminderOverviewResponse response = patientReminderService.getReminderOverview();
|
|
|
+ return R.success(200, "ok", response);
|
|
|
+ } catch (Exception e) {
|
|
|
+ logger.error("get patient reminder overview 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 = 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<?> savePatientReminder(@RequestBody CreatePatientReminderRequest req) {
|
|
|
+ try {
|
|
|
+ patientReminderService.savePatientReminder(req);
|
|
|
+ return R.success(200, "患者提醒设置保存成功");
|
|
|
+ } catch (Exception e) {
|
|
|
+ logger.error("save patient reminder failed", e);
|
|
|
+ return R.fail(ErrorCode.SYSTEM_ERROR.getCode(), ErrorCode.SYSTEM_ERROR.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Operation(summary = "删除患者提醒设置", description = "根据ID删除患者提醒设置")
|
|
|
+ @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)))
|
|
|
+ })
|
|
|
+ @DeleteMapping(path = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
|
|
|
+ public R<?> deletePatientReminder(@PathVariable Long id) {
|
|
|
+ try {
|
|
|
+ patientReminderService.deletePatientReminder(id);
|
|
|
+ return R.success(200, "患者提醒设置删除成功");
|
|
|
+ } catch (Exception e) {
|
|
|
+ logger.error("delete patient reminder failed", e);
|
|
|
+ return R.fail(ErrorCode.SYSTEM_ERROR.getCode(), ErrorCode.SYSTEM_ERROR.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+## 8. 错误码补充
|
|
|
+
|
|
|
+在 ErrorCode 枚举中可能需要添加以下错误码:
|
|
|
+
|
|
|
+```java
|
|
|
+// 在 ErrorCode.java 中添加
|
|
|
+PATIENT_REMINDER_NOT_FOUND(7000, "患者提醒记录不存在"),
|
|
|
+PATIENT_REMINDER_ACCESS_DENIED(7001, "无权访问患者提醒记录");
|
|
|
+```
|
|
|
+
|
|
|
+## 9. 接口调用示例
|
|
|
+
|
|
|
+### 9.1 获取提醒概览
|
|
|
+```
|
|
|
+GET /patient-reminder/overview
|
|
|
+```
|
|
|
+
|
|
|
+响应示例:
|
|
|
+```json
|
|
|
+{
|
|
|
+ "code": 200,
|
|
|
+ "message": "ok",
|
|
|
+ "data": {
|
|
|
+ "reminder": {
|
|
|
+ "id": "1988502746686595073",
|
|
|
+ "patientUserId": "1988172854356631553",
|
|
|
+ "notificationEnabled": true,
|
|
|
+ "subscriptionAvailable": true,
|
|
|
+ "bloodPressureEnabled": true,
|
|
|
+ "bloodPressureTimes": ["07:00", "18:00"],
|
|
|
+ "bloodSugarEnabled": false,
|
|
|
+ "bloodSugarTimes": ["12:00"],
|
|
|
+ "heartRateEnabled": true,
|
|
|
+ "heartRateTimes": ["18:00"],
|
|
|
+ "medicationEnabled": true,
|
|
|
+ "createTime": "2025-11-20T10:00:00"
|
|
|
+ }
|
|
|
+ },
|
|
|
+ "timestamp": 1763570208009,
|
|
|
+ "requestId": "xxx",
|
|
|
+ "traceId": "xxx"
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 9.2 保存患者提醒设置
|
|
|
+```
|
|
|
+POST /patient-reminder/save
|
|
|
+{
|
|
|
+ "notificationEnabled": true,
|
|
|
+ "subscriptionAvailable": true,
|
|
|
+ "bloodPressureEnabled": true,
|
|
|
+ "bloodPressureTimes": ["07:00", "18:00"],
|
|
|
+ "bloodSugarEnabled": false,
|
|
|
+ "bloodSugarTimes": ["12:00"],
|
|
|
+ "heartRateEnabled": true,
|
|
|
+ "heartRateTimes": ["18:00"],
|
|
|
+ "medicationEnabled": true
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 9.3 删除患者提醒设置
|
|
|
+```
|
|
|
+DELETE /patient-reminder/1988502746686595073
|
|
|
+```
|
|
|
+
|
|
|
+## 10. 权限设计
|
|
|
+
|
|
|
+1. **患者**:
|
|
|
+ - 可以查看自己的提醒设置
|
|
|
+ - 可以创建、更新、删除自己的提醒设置
|
|
|
+
|
|
|
+2. **医生**:
|
|
|
+ - 不能直接操作患者的提醒设置
|
|
|
+ - 可以在患者健康档案中查看提醒设置情况
|
|
|
+
|
|
|
+3. **系统管理员**:
|
|
|
+ - 可以查看所有患者的提醒设置
|
|
|
+ - 可以进行系统级的提醒配置管理
|
|
|
+
|
|
|
+## 11. 安全考虑
|
|
|
+
|
|
|
+本项目安全实现应遵循 `docs/OLD/DevRule/08-安全规范.md` 中的已有约定,以下为与提醒管理功能相关的要点(需在实现时落地):
|
|
|
+
|
|
|
+1. **认证与授权**:
|
|
|
+ - 使用 Token(优先 `Authorization: Bearer <token>`,兼容 `X-Token` / `token`)并通过 `AuthInterceptor` 校验,将 `currentUserId`/`currentUserRole` 注入请求上下文。
|
|
|
+ - 仅允许患者访问自己的提醒设置。
|
|
|
+
|
|
|
+2. **输入校验**:
|
|
|
+ - 使用 Bean Validation 注解对请求参数进行校验。
|
|
|
+ - 对时间格式进行严格校验,确保符合 HH:mm 格式。
|
|
|
+ - 对JSON格式的时间点列表进行有效性校验。
|
|
|
+
|
|
|
+3. **异常与统一错误响应**:
|
|
|
+ - 使用全局异常处理器统一映射异常到 `R.fail(code, message)`,并返回合适的 HTTP 状态码。
|
|
|
+
|
|
|
+4. **日志与敏感信息**:
|
|
|
+ - 在日志中避免记录完整 Token 或敏感字段。
|
|
|
+
|
|
|
+5. **SQL 注入与 ORM 使用**:
|
|
|
+ - 使用 MyBatis-Plus 提供的预编译与条件构造器,禁止使用字符串拼接构建 SQL。
|
|
|
+
|
|
|
+6. **最小权限原则**:
|
|
|
+ - 服务端对每个接口、数据读取与修改操作都进行基于角色的授权校验。
|
|
|
+
|
|
|
+7. **审计与监控**:
|
|
|
+ - 对关键操作进行日志记录,便于审计和问题排查。
|