Compare commits
2 Commits
303cd01a59
...
a12cc4c905
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a12cc4c905 | ||
|
|
b36a92c981 |
@@ -229,7 +229,8 @@ aliyun:
|
|||||||
# VMS 语音通知配置
|
# VMS 语音通知配置
|
||||||
vms:
|
vms:
|
||||||
# 是否启用 MNS 回扇消费(true 时自动启动 MNS 消费线程接收 VMS 回扇)
|
# 是否启用 MNS 回扇消费(true 时自动启动 MNS 消费线程接收 VMS 回扇)
|
||||||
mns-enabled: true
|
# 注意:测试服设为 false,避免与正式服共抢同一个 MNS 队列导致回执随机丢失
|
||||||
|
mns-enabled: false
|
||||||
# VMS 语音服务对应的 AccessKey ID(如果与上面 IoT 平台的 AK 相同可不填)
|
# VMS 语音服务对应的 AccessKey ID(如果与上面 IoT 平台的 AK 相同可不填)
|
||||||
access-key-id: LTAI5tKh6fEKJJESaTdnChkh
|
access-key-id: LTAI5tKh6fEKJJESaTdnChkh
|
||||||
# VMS 语音服务对应的 AccessKey Secret
|
# VMS 语音服务对应的 AccessKey Secret
|
||||||
|
|||||||
@@ -50,8 +50,8 @@ public class VmsMnsConsumerServiceImpl implements VmsMnsConsumerService {
|
|||||||
/** MNS 消息类型(固定值) */
|
/** MNS 消息类型(固定值) */
|
||||||
private static final String VMS_MNS_MESSAGE_TYPE = "VoiceReport";
|
private static final String VMS_MNS_MESSAGE_TYPE = "VoiceReport";
|
||||||
|
|
||||||
/** MNS 默认线程数 */
|
/** MNS 默认线程数改为1,避免多线程共享 mnsClient 时互相踢连接导致无限崩溃 */
|
||||||
private static final int MNS_THREAD_COUNT = 2;
|
private static final int MNS_THREAD_COUNT = 1;
|
||||||
private static final int POLL_WAIT_SECONDS = 30;
|
private static final int POLL_WAIT_SECONDS = 30;
|
||||||
|
|
||||||
/** Token 过期缓冲时间(120秒),小于此值则重新获取,防止服务器时间差异 */
|
/** Token 过期缓冲时间(120秒),小于此值则重新获取,防止服务器时间差异 */
|
||||||
|
|||||||
@@ -43,13 +43,21 @@ public class VmsNoticeServiceImpl implements VmsNoticeService {
|
|||||||
if (vmsClient == null) {
|
if (vmsClient == null) {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
if (vmsClient == null) {
|
if (vmsClient == null) {
|
||||||
|
// 必须使用 VMS 专用 AK(vms.access-key-id),而非 IoT 平台 AK
|
||||||
|
// MNS 回执消费也使用 VMS 专用 AK,两端必须一致,否则回执无法匹配
|
||||||
|
AliyunIotProperties.VmsMnsConfig vmsCfg = aliyunIotProperties.getVms();
|
||||||
|
String akId = (vmsCfg.getAccessKeyId() != null && !vmsCfg.getAccessKeyId().isEmpty())
|
||||||
|
? vmsCfg.getAccessKeyId() : aliyunIotProperties.getAccessKeyId();
|
||||||
|
String akSecret = (vmsCfg.getAccessKeySecret() != null && !vmsCfg.getAccessKeySecret().isEmpty())
|
||||||
|
? vmsCfg.getAccessKeySecret() : aliyunIotProperties.getAccessKeySecret();
|
||||||
|
// VMS 服务固定在 cn-hangzhou,与 IoT 平台 region 无关
|
||||||
DefaultProfile profile = DefaultProfile.getProfile(
|
DefaultProfile profile = DefaultProfile.getProfile(
|
||||||
"cn-hangzhou",
|
"cn-hangzhou",
|
||||||
aliyunIotProperties.getAccessKeyId(),
|
akId,
|
||||||
aliyunIotProperties.getAccessKeySecret()
|
akSecret
|
||||||
);
|
);
|
||||||
vmsClient = new DefaultAcsClient(profile);
|
vmsClient = new DefaultAcsClient(profile);
|
||||||
log.info("VMS 客户端初始化成功");
|
log.info("VMS 客户端初始化成功, akId={}", akId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,10 +57,20 @@ public class WarnCallNoticeServiceImpl implements WarnCallNoticeService {
|
|||||||
/**
|
/**
|
||||||
* 回执超时时间(分钟)
|
* 回执超时时间(分钟)
|
||||||
* 从呼叫实际发出(callTime 已修正为真实发出时间)到收到 VMS 回执的最长等待时间。
|
* 从呼叫实际发出(callTime 已修正为真实发出时间)到收到 VMS 回执的最长等待时间。
|
||||||
* 正常链路:通话约 30s + VMS 生成回执约 1~2min,3 分钟内完成。
|
* 正常链路:通话约 30s + VMS 生成回执约 1~2min,通常 3 分钟内完成。
|
||||||
* 设为 5 分钟,兼顾回执延迟容忍与批次重试及时性(批次间隔 3 分钟)。
|
* 但阿里云 VMS 在网络波动或平台繁忙时,回执延迟可达 5~10 分钟,因此设为 15 分钟。
|
||||||
|
* 这意味着同批次最多等待 15 分钟才会解除阻塞让下一条记录发出,
|
||||||
|
* 整体上 15 分钟内不会对同一设备重复拨打,是可接受的保守策略。
|
||||||
*/
|
*/
|
||||||
private static final int CALLBACK_TIMEOUT_MINUTES = 5;
|
private static final int CALLBACK_TIMEOUT_MINUTES = 15;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批次阻断查询窗口(分钟)
|
||||||
|
* 用于定时任务发出待发记录前,向前扫描同设备是否已有处于这个窗口内的阻塞记录。
|
||||||
|
* 计算依据:回执超时(15min) + 批次最大时长(3次重试 x 2个手机号 x 3min = 18min) + 缓冲 = 40min
|
||||||
|
* 确保给定批次的所有记录都在窗口内,防止批次尾部记录因窗口过短而脱革导致重复发出。
|
||||||
|
*/
|
||||||
|
private static final int BATCH_BLOCK_WINDOW_MINUTES = 40;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
@@ -260,11 +270,11 @@ public class WarnCallNoticeServiceImpl implements WarnCallNoticeService {
|
|||||||
for (AquWarnCallNotice notice : pendingList) {
|
for (AquWarnCallNotice notice : pendingList) {
|
||||||
try {
|
try {
|
||||||
// 发出前先检查同设备同批次窗口内的状态
|
// 发出前先检查同设备同批次窗口内的状态
|
||||||
// 批次窗口 = 当前记录的 callTime 往前 CALLBACK_TIMEOUT_MINUTES 分钟(足够覆盖整个批次)
|
// 批次窗口 = 当前记录的 callTime 往前 BATCH_BLOCK_WINDOW_MINUTES 分钟(足够覆盖整个批次)
|
||||||
// - status=1(等待回执中):前一条已发出但回执未到,不重复拨打,等其超时后再发
|
// - status=1(等待回执中):前一条已发出但回执未到,不重复拨打,等其超时后再发
|
||||||
// - status=3(已成功):已有成功回执,跳过该条记录
|
// - status=3(已成功):已有成功回执,跳过该条记录
|
||||||
if (notice.getDeviceId() != null) {
|
if (notice.getDeviceId() != null) {
|
||||||
LocalDateTime batchWindowStart = notice.getCallTime().minusMinutes(CALLBACK_TIMEOUT_MINUTES);
|
LocalDateTime batchWindowStart = notice.getCallTime().minusMinutes(BATCH_BLOCK_WINDOW_MINUTES);
|
||||||
long blockingCount = warnCallNoticeMapper.countWaitingOrSuccessByDeviceAfter(
|
long blockingCount = warnCallNoticeMapper.countWaitingOrSuccessByDeviceAfter(
|
||||||
notice.getDeviceId(), batchWindowStart);
|
notice.getDeviceId(), batchWindowStart);
|
||||||
if (blockingCount > 0) {
|
if (blockingCount > 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user