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

@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.intc</groupId>
<artifactId>intc-common</artifactId>
<version>3.6.3</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>intc-common-message</artifactId>
<description>
intc-common-message 消息通知服务(短信、语音等)
</description>
<dependencies>
<!-- 阿里云短信 SDK -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>dysmsapi20170525</artifactId>
<version>2.0.24</version>
</dependency>
<!-- 阿里云语音 SDK -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>dyvmsapi20170525</artifactId>
<version>2.0.2</version>
</dependency>
<!-- 阿里云核心 SDK -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>tea-openapi</artifactId>
<version>0.3.4</version>
</dependency>
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- Fastjson -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
</dependency>
<!-- intc-common-core -->
<dependency>
<groupId>com.intc</groupId>
<artifactId>intc-common-core</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,17 @@
package com.intc.common.message.config;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* 阿里云消息服务自动配置
*
* @author bot5
* @date 2026-03-29
*/
@Configuration
@EnableConfigurationProperties(AliyunMessageProperties.class)
@ComponentScan(basePackages = "com.intc.common.message")
public class AliyunMessageAutoConfiguration {
}

View File

@@ -0,0 +1,78 @@
package com.intc.common.message.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 阿里云消息服务配置
*
* @author bot5
* @date 2026-03-29
*/
@Data
@Component
@ConfigurationProperties(prefix = "aliyun.message")
public class AliyunMessageProperties {
/**
* 阿里云 AccessKey ID
*/
private String accessKeyId;
/**
* 阿里云 AccessKey Secret
*/
private String accessKeySecret;
/**
* 短信服务配置
*/
private SmsConfig sms = new SmsConfig();
/**
* 语音服务配置
*/
private VoiceConfig voice = new VoiceConfig();
@Data
public static class SmsConfig {
/**
* 短信签名名称
*/
private String signName;
/**
* 短信模板CODE用药提醒
*/
private String medicationRemindTemplateCode;
/**
* 短信模板CODE漏服提醒
*/
private String missedRemindTemplateCode;
/**
* 短信模板CODE家属通知
*/
private String familyNotifyTemplateCode;
}
@Data
public static class VoiceConfig {
/**
* 语音通知模板CODE用药提醒
*/
private String medicationRemindTtsCode;
/**
* 语音通知模板CODE漏服提醒
*/
private String missedRemindTtsCode;
/**
* 被叫号码显示的呼入号码
*/
private String calledShowNumber;
}
}

View File

@@ -0,0 +1,129 @@
package com.intc.common.message.service;
import com.alibaba.fastjson2.JSON;
import com.aliyun.dysmsapi20170525.Client;
import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
import com.aliyun.teaopenapi.models.Config;
import com.intc.common.message.config.AliyunMessageProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
/**
* 阿里云短信服务
*
* @author bot5
* @date 2026-03-29
*/
@Service
public class SmsService {
private static final Logger log = LoggerFactory.getLogger(SmsService.class);
@Resource
private AliyunMessageProperties properties;
private Client smsClient;
@PostConstruct
public void init() throws Exception {
Config config = new Config()
.setAccessKeyId(properties.getAccessKeyId())
.setAccessKeySecret(properties.getAccessKeySecret())
.setEndpoint("dysmsapi.aliyuncs.com");
this.smsClient = new Client(config);
}
/**
* 发送用药提醒短信
*
* @param phoneNumber 手机号
* @param personName 成员姓名
* @param medicineName 药品名称
* @param plannedTime 计划服药时间
* @return 是否成功
*/
public boolean sendMedicationRemind(String phoneNumber, String personName, String medicineName, String plannedTime) {
Map<String, String> templateParams = new HashMap<>();
templateParams.put("name", personName);
templateParams.put("medicine", medicineName);
templateParams.put("time", plannedTime);
return sendSms(phoneNumber, properties.getSms().getMedicationRemindTemplateCode(), templateParams);
}
/**
* 发送漏服提醒短信
*
* @param phoneNumber 手机号
* @param personName 成员姓名
* @param medicineName 药品名称
* @param plannedTime 计划服药时间
* @return 是否成功
*/
public boolean sendMissedRemind(String phoneNumber, String personName, String medicineName, String plannedTime) {
Map<String, String> templateParams = new HashMap<>();
templateParams.put("name", personName);
templateParams.put("medicine", medicineName);
templateParams.put("time", plannedTime);
return sendSms(phoneNumber, properties.getSms().getMissedRemindTemplateCode(), templateParams);
}
/**
* 发送家属通知短信
*
* @param phoneNumber 手机号
* @param personName 成员姓名
* @param medicineName 药品名称
* @param plannedTime 计划服药时间
* @return 是否成功
*/
public boolean sendFamilyNotify(String phoneNumber, String personName, String medicineName, String plannedTime) {
Map<String, String> templateParams = new HashMap<>();
templateParams.put("name", personName);
templateParams.put("medicine", medicineName);
templateParams.put("time", plannedTime);
return sendSms(phoneNumber, properties.getSms().getFamilyNotifyTemplateCode(), templateParams);
}
/**
* 发送短信
*
* @param phoneNumber 手机号
* @param templateCode 模板CODE
* @param templateParams 模板参数
* @return 是否成功
*/
public boolean sendSms(String phoneNumber, String templateCode, Map<String, String> templateParams) {
try {
SendSmsRequest request = new SendSmsRequest()
.setPhoneNumbers(phoneNumber)
.setSignName(properties.getSms().getSignName())
.setTemplateCode(templateCode)
.setTemplateParam(JSON.toJSONString(templateParams));
SendSmsResponse response = smsClient.sendSms(request);
if ("OK".equals(response.getBody().getCode())) {
log.info("短信发送成功: phone={}, templateCode={}, bizId={}",
phoneNumber, templateCode, response.getBody().getBizId());
return true;
} else {
log.error("短信发送失败: phone={}, code={}, message={}",
phoneNumber, response.getBody().getCode(), response.getBody().getMessage());
return false;
}
} catch (Exception e) {
log.error("短信发送异常: phone={}, templateCode={}", phoneNumber, templateCode, e);
return false;
}
}
}

View File

@@ -0,0 +1,111 @@
package com.intc.common.message.service;
import com.alibaba.fastjson2.JSON;
import com.aliyun.dyvmsapi20170525.Client;
import com.aliyun.dyvmsapi20170525.models.SingleCallByTtsRequest;
import com.aliyun.dyvmsapi20170525.models.SingleCallByTtsResponse;
import com.aliyun.teaopenapi.models.Config;
import com.intc.common.message.config.AliyunMessageProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
/**
* 阿里云语音通知服务
*
* @author bot5
* @date 2026-03-29
*/
@Service
public class VoiceService {
private static final Logger log = LoggerFactory.getLogger(VoiceService.class);
@Resource
private AliyunMessageProperties properties;
private Client voiceClient;
@PostConstruct
public void init() throws Exception {
Config config = new Config()
.setAccessKeyId(properties.getAccessKeyId())
.setAccessKeySecret(properties.getAccessKeySecret())
.setEndpoint("dyvmsapi.aliyuncs.com");
this.voiceClient = new Client(config);
}
/**
* 发送用药提醒语音电话
*
* @param phoneNumber 手机号
* @param personName 成员姓名
* @param medicineName 药品名称
* @param plannedTime 计划服药时间
* @return 是否成功
*/
public boolean callMedicationRemind(String phoneNumber, String personName, String medicineName, String plannedTime) {
Map<String, String> ttsParams = new HashMap<>();
ttsParams.put("name", personName);
ttsParams.put("medicine", medicineName);
ttsParams.put("time", plannedTime);
return callByTts(phoneNumber, properties.getVoice().getMedicationRemindTtsCode(), ttsParams);
}
/**
* 发送漏服提醒语音电话
*
* @param phoneNumber 手机号
* @param personName 成员姓名
* @param medicineName 药品名称
* @param plannedTime 计划服药时间
* @return 是否成功
*/
public boolean callMissedRemind(String phoneNumber, String personName, String medicineName, String plannedTime) {
Map<String, String> ttsParams = new HashMap<>();
ttsParams.put("name", personName);
ttsParams.put("medicine", medicineName);
ttsParams.put("time", plannedTime);
return callByTts(phoneNumber, properties.getVoice().getMissedRemindTtsCode(), ttsParams);
}
/**
* 发送语音通知电话
*
* @param phoneNumber 手机号
* @param ttsCode 语音模板CODE
* @param ttsParams 模板参数
* @return 是否成功
*/
public boolean callByTts(String phoneNumber, String ttsCode, Map<String, String> ttsParams) {
try {
SingleCallByTtsRequest request = new SingleCallByTtsRequest()
.setCalledNumber(phoneNumber)
.setCalledShowNumber(properties.getVoice().getCalledShowNumber())
.setTtsCode(ttsCode)
.setTtsParam(JSON.toJSONString(ttsParams));
SingleCallByTtsResponse response = voiceClient.singleCallByTts(request);
if ("OK".equals(response.getBody().getCode())) {
log.info("语音电话发送成功: phone={}, ttsCode={}, callId={}",
phoneNumber, ttsCode, response.getBody().getCallId());
return true;
} else {
log.error("语音电话发送失败: phone={}, code={}, message={}",
phoneNumber, response.getBody().getCode(), response.getBody().getMessage());
return false;
}
} catch (Exception e) {
log.error("语音电话发送异常: phone={}, ttsCode={}", phoneNumber, ttsCode, e);
return false;
}
}
}

View File

@@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.intc.common.message.config.AliyunMessageAutoConfiguration

View File

@@ -17,6 +17,7 @@
<module>intc-common-security</module>
<module>intc-common-datascope</module>
<module>intc-common-datasource</module>
<module>intc-common-message</module>
</modules>
<artifactId>intc-common</artifactId>