feat(medication): 实现用药提醒短信和电话提醒功能

- 新增 intc-common-message 消息服务模块
- 阿里云短信服务 SmsService(用药提醒、漏服提醒、家属通知)
- 阿里云语音服务 VoiceService(用药提醒、漏服提醒电话)
- HealthPerson 添加 phone 手机号字段
- MedicationPlan 添加 remind_type 提醒方式字段(1-短信 2-电话 3-短信+电话)
- MedicationRemindJob 完善提醒逻辑:
  - 定时扫描待服药任务发送提醒
  - 超时30分钟未服药发送加强提醒
  - 配置家属通知功能
- 新增数据库更新SQL和Nacos配置示例文件
This commit is contained in:
bot5
2026-03-29 18:26:53 +08:00
parent f0a61a5314
commit 846cd3141e
19 changed files with 662 additions and 12 deletions

View File

@@ -100,6 +100,13 @@
<groupId>com.intc</groupId>
<artifactId>intc-common-swagger</artifactId>
</dependency>
<!-- intc-common-message 消息服务 -->
<dependency>
<groupId>com.intc</groupId>
<artifactId>intc-common-message</artifactId>
</dependency>
<dependency>
<groupId>ws.schild</groupId>
<artifactId>jave-core</artifactId>

View File

@@ -81,6 +81,11 @@ public class HealthPerson extends BaseEntity
@Excel(name = "身份证")
private String identityCard;
/** 手机号 */
@ApiModelProperty(value="手机号")
@Excel(name = "手机号")
private String phone;
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
@@ -100,6 +105,7 @@ public class HealthPerson extends BaseEntity
.append("ranking", getRanking())
.append("sex", getSex())
.append("identityCard", getIdentityCard())
.append("phone", getPhone())
.toString();
}
}

View File

@@ -66,6 +66,9 @@ public class MedicationPlan extends BaseEntity {
@ApiModelProperty("是否开启提醒1-是 0-否")
private Integer remindEnabled;
@ApiModelProperty("提醒方式1-短信 2-电话 3-短信+电话")
private Integer remindType;
@ApiModelProperty("提前提醒分钟数")
private Integer remindAdvanceMin;

View File

@@ -72,6 +72,9 @@ public class MedicationPlanVo {
@ApiModelProperty("是否开启提醒1-是 0-否")
private Integer remindEnabled;
@ApiModelProperty("提醒方式1-短信 2-电话 3-短信+电话")
private Integer remindType;
@ApiModelProperty("提前提醒分钟数")
private Integer remindAdvanceMin;

View File

@@ -33,6 +33,9 @@ public class MedicationTaskVo {
@ApiModelProperty("成员名称")
private String personName;
@ApiModelProperty("成员手机号")
private String personPhone;
@ApiModelProperty("药品名称")
private String medicineName;

View File

@@ -1,15 +1,24 @@
package com.intc.health.task;
import com.fasterxml.jackson.databind.JsonNode;
import com.intc.common.message.service.SmsService;
import com.intc.common.message.service.VoiceService;
import com.intc.health.domain.MedicationTask;
import com.intc.health.domain.vo.HealthPersonVo;
import com.intc.health.domain.vo.MedicationPlanVo;
import com.intc.health.domain.vo.MedicationTaskVo;
import com.intc.health.mapper.HealthPersonMapper;
import com.intc.health.mapper.MedicationPlanMapper;
import com.intc.health.mapper.MedicationTaskMapper;
import com.intc.health.service.IHealthPersonService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
/**
@@ -26,6 +35,20 @@ public class MedicationRemindJob {
@Resource
private MedicationTaskMapper taskMapper;
@Resource
private MedicationPlanMapper planMapper;
@Resource
private HealthPersonMapper personMapper;
@Resource
private SmsService smsService;
@Resource
private VoiceService voiceService;
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm");
/**
* 扫描并发送提醒
* 定时任务配置:每分钟执行
@@ -63,11 +86,59 @@ public class MedicationRemindJob {
task.getPersonName(), task.getMedicineName(),
task.getPlannedDate(), task.getPlannedTime());
// TODO: 实现消息推送
// 1. 查询用户的提醒设置
// 2. 根据设置选择推送渠道
// 3. 发送推送消息
// 4. 记录提醒日志
// 获取用药计划配置
MedicationPlanVo plan = planMapper.selectById(task.getPlanId());
if (plan == null) {
log.warn("用药计划不存在: planId={}", task.getPlanId());
return;
}
// 检查是否开启提醒
if (plan.getRemindEnabled() == null || plan.getRemindEnabled() == 0) {
log.debug("用药计划未开启提醒: planId={}", task.getPlanId());
return;
}
// 获取成员手机号
String phone = task.getPersonPhone();
if (phone == null || phone.isEmpty()) {
log.warn("成员手机号为空: personId={}", task.getPersonId());
return;
}
// 格式化计划时间
String plannedTimeStr = formatPlannedTime(task);
// 提醒方式1-短信 2-电话 3-短信+电话
Integer remindType = plan.getRemindType();
if (remindType == null) {
remindType = 1; // 默认短信
}
boolean smsSuccess = false;
boolean voiceSuccess = false;
// 发送短信提醒
if (remindType == 1 || remindType == 3) {
smsSuccess = smsService.sendMedicationRemind(phone, task.getPersonName(), task.getMedicineName(), plannedTimeStr);
log.info("短信提醒发送结果: phone={}, success={}", phone, smsSuccess);
}
// 发送电话提醒
if (remindType == 2 || remindType == 3) {
voiceSuccess = voiceService.callMedicationRemind(phone, task.getPersonName(), task.getMedicineName(), plannedTimeStr);
log.info("电话提醒发送结果: phone={}, success={}", phone, voiceSuccess);
}
// 更新任务提醒状态
MedicationTask updateTask = new MedicationTask();
updateTask.setId(task.getId());
updateTask.setRemindStatus(1); // 已提醒
updateTask.setRemindTime(LocalDateTime.now());
taskMapper.update(updateTask);
log.info("提醒发送完成: taskId={}, person={}, sms={}, voice={}",
task.getId(), task.getPersonName(), smsSuccess, voiceSuccess);
}
/**
@@ -109,8 +180,98 @@ public class MedicationRemindJob {
task.getPersonName(), task.getMedicineName(),
task.getPlannedDate(), task.getPlannedTime());
// TODO: 实现超时提醒逻辑
// 1. 发送二次提醒
// 2. 如果配置了家属通知,通知家属
// 获取用药计划配置
MedicationPlanVo plan = planMapper.selectById(task.getPlanId());
if (plan == null) {
log.warn("用药计划不存在: planId={}", task.getPlanId());
return;
}
// 获取成员手机号
String phone = task.getPersonPhone();
if (phone == null || phone.isEmpty()) {
log.warn("成员手机号为空: personId={}", task.getPersonId());
return;
}
// 格式化计划时间
String plannedTimeStr = formatPlannedTime(task);
// 提醒方式:超时提醒默认短信+电话
Integer remindType = plan.getRemindType();
if (remindType == null) {
remindType = 3; // 默认短信+电话
} else if (remindType == 1) {
remindType = 3; // 短信改为短信+电话加强提醒
}
// 发送超时短信提醒
boolean smsSuccess = smsService.sendMissedRemind(phone, task.getPersonName(), task.getMedicineName(), plannedTimeStr);
log.info("超时短信提醒发送结果: phone={}, success={}", phone, smsSuccess);
// 发送超时电话提醒
if (remindType == 2 || remindType == 3) {
boolean voiceSuccess = voiceService.callMissedRemind(phone, task.getPersonName(), task.getMedicineName(), plannedTimeStr);
log.info("超时电话提醒发送结果: phone={}, success={}", phone, voiceSuccess);
}
// 更新任务提醒状态为已超时提醒
MedicationTask updateTask = new MedicationTask();
updateTask.setId(task.getId());
updateTask.setRemindStatus(2); // 已超时提醒
updateTask.setRemindTime(LocalDateTime.now());
taskMapper.update(updateTask);
// 如果配置了家属通知,通知家属
if (plan.getNotifyFamily() != null && plan.getNotifyFamily() == 1) {
notifyFamily(task, plan);
}
log.info("超时提醒发送完成: taskId={}, person={}", task.getId(), task.getPersonName());
}
/**
* 通知家属
*/
private void notifyFamily(MedicationTaskVo task, MedicationPlanVo plan) {
JsonNode familyMemberIds = plan.getFamilyMemberIds();
if (familyMemberIds == null || !familyMemberIds.isArray()) {
log.debug("未配置家属成员");
return;
}
String plannedTimeStr = formatPlannedTime(task);
for (JsonNode memberIdNode : familyMemberIds) {
Long memberId = memberIdNode.asLong();
HealthPersonVo familyMember = personMapper.selectHealthPersonById(memberId);
if (familyMember == null) {
log.warn("家属成员不存在: memberId={}", memberId);
continue;
}
// 获取家属手机号需要在HealthPersonVo中添加phone字段
String familyPhone = familyMember.getPhone();
if (familyPhone == null || familyPhone.isEmpty()) {
log.warn("家属手机号为空: memberId={}", memberId);
continue;
}
// 发送家属通知短信
boolean success = smsService.sendFamilyNotify(familyPhone, task.getPersonName(), task.getMedicineName(), plannedTimeStr);
log.info("家属通知发送结果: familyPhone={}, familyMember={}, success={}",
familyPhone, familyMember.getName(), success);
}
}
/**
* 格式化计划时间
*/
private String formatPlannedTime(MedicationTaskVo task) {
if (task.getPlannedTime() != null) {
// plannedTime是Date类型需要转换
return new java.text.SimpleDateFormat("HH:mm").format(task.getPlannedTime());
}
return "未知时间";
}
}

View File

@@ -21,10 +21,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="ranking" column="ranking" />
<result property="sex" column="sex" />
<result property="identityCard" column="identity_card" />
<result property="phone" column="phone" />
</resultMap>
<sql id="selectHealthPersonVo">
select a.id, a.name,a.sex,a.identity_card, a.type, a.create_by, a.create_time, a.update_by, a.update_time, a.del_flag, a.remark, a.birthday, a.nick_name, a.height, a.weight, a.ranking from health_person a
select a.id, a.name,a.sex,a.identity_card, a.phone, a.type, a.create_by, a.create_time, a.update_by, a.update_time, a.del_flag, a.remark, a.birthday, a.nick_name, a.height, a.weight, a.ranking from health_person a
</sql>
<select id="selectHealthPersonList" parameterType="HealthPersonDto" resultMap="HealthPersonResult">
@@ -64,6 +65,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="ranking != null">ranking,</if>
<if test="sex != null and sex != ''">sex,</if>
<if test="identityCard != null and identityCard != ''">identity_card,</if>
<if test="phone != null and phone != ''">phone,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">#{id},</if>
@@ -82,6 +84,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="ranking != null">#{ranking},</if>
<if test="sex != null and sex != ''">#{sex},</if>
<if test="identityCard != null and identityCard != ''">#{identityCard},</if>
<if test="phone != null and phone != ''">#{phone},</if>
</trim>
</insert>
@@ -103,6 +106,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="ranking != null">ranking = #{ranking},</if>
<if test="sex != null and sex != ''">sex = #{sex},</if>
<if test="identityCard != null and identityCard != ''">identity_card = #{identityCard},</if>
<if test="phone != null and phone != ''">phone = #{phone},</if>
</trim>
where id = #{id}
</update>

View File

@@ -20,6 +20,7 @@
<result property="startDate" column="start_date"/>
<result property="endDate" column="end_date"/>
<result property="remindEnabled" column="remind_enabled"/>
<result property="remindType" column="remind_type"/>
<result property="remindAdvanceMin" column="remind_advance_min"/>
<result property="notifyFamily" column="notify_family"/>
<result property="familyMemberIds" column="family_member_ids" typeHandler="com.intc.common.core.handler.JsonNodeTypeHandler"/>
@@ -35,7 +36,7 @@
c.disease_name as chronic_disease_name, a.plan_name, a.medicine_id,
a.medicine_name, a.specification, a.dosage_per_time, a.dosage_unit,
a.frequency_type, a.frequency_config, a.time_slots, a.start_date, a.end_date,
a.remind_enabled, a.remind_advance_min, a.notify_family, a.family_member_ids,
a.remind_enabled, a.remind_type, a.remind_advance_min, a.notify_family, a.family_member_ids,
a.status, a.create_by, a.create_time,
(select count(1) from medication_task t where t.plan_id = a.id and t.del_flag = 0 and t.planned_date = current_date) as today_total,
(select count(1) from medication_task t where t.plan_id = a.id and t.del_flag = 0 and t.planned_date = current_date and t.status = 1) as today_completed
@@ -94,6 +95,7 @@
<if test="startDate != null">start_date,</if>
<if test="endDate != null">end_date,</if>
<if test="remindEnabled != null">remind_enabled,</if>
<if test="remindType != null">remind_type,</if>
<if test="remindAdvanceMin != null">remind_advance_min,</if>
<if test="notifyFamily != null">notify_family,</if>
<if test="familyMemberIds != null">family_member_ids,</if>
@@ -118,6 +120,7 @@
<if test="startDate != null">#{startDate},</if>
<if test="endDate != null">#{endDate},</if>
<if test="remindEnabled != null">#{remindEnabled},</if>
<if test="remindType != null">#{remindType},</if>
<if test="remindAdvanceMin != null">#{remindAdvanceMin},</if>
<if test="notifyFamily != null">#{notifyFamily},</if>
<if test="familyMemberIds != null">#{familyMemberIds, typeHandler=com.intc.common.core.handler.JsonNodeTypeHandler},</if>
@@ -146,6 +149,7 @@
<if test="startDate != null">start_date = #{startDate},</if>
<if test="endDate != null">end_date = #{endDate},</if>
<if test="remindEnabled != null">remind_enabled = #{remindEnabled},</if>
<if test="remindType != null">remind_type = #{remindType},</if>
<if test="remindAdvanceMin != null">remind_advance_min = #{remindAdvanceMin},</if>
<if test="notifyFamily != null">notify_family = #{notifyFamily},</if>
<if test="familyMemberIds != null">family_member_ids = #{familyMemberIds, typeHandler=com.intc.common.core.handler.JsonNodeTypeHandler},</if>

View File

@@ -8,6 +8,7 @@
<result property="planName" column="plan_name"/>
<result property="personId" column="person_id"/>
<result property="personName" column="person_name"/>
<result property="personPhone" column="person_phone"/>
<result property="medicineName" column="medicine_name"/>
<result property="plannedDate" column="planned_date"/>
<result property="plannedTime" column="planned_time"/>
@@ -23,7 +24,7 @@
</resultMap>
<sql id="selectVo">
select a.id, a.plan_id, p.plan_name, a.person_id, m.name as person_name,
select a.id, a.plan_id, p.plan_name, a.person_id, m.name as person_name, m.phone as person_phone,
a.medicine_name, a.planned_date, a.planned_time, a.planned_dosage,
a.actual_time, a.actual_dosage, a.status, a.confirm_type, a.notes,
a.remind_status, a.remind_time, a.create_time