|
|
@@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSONArray;
|
|
|
import com.alibaba.fastjson.JSONObject;
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
import com.fs.common.core.domain.R;
|
|
|
+import com.fs.common.core.domain.entity.SysDictData;
|
|
|
import com.fs.common.core.redis.RedisCache;
|
|
|
import com.fs.common.service.ISmsService;
|
|
|
import com.fs.company.domain.CompanySmsLogs;
|
|
|
@@ -13,10 +14,13 @@ import com.fs.his.domain.FsUser;
|
|
|
import com.fs.his.dto.SendResultDetailDTO;
|
|
|
import com.fs.his.service.IFsUserService;
|
|
|
import com.fs.his.utils.PhoneUtil;
|
|
|
+import com.fs.qw.domain.QwExternalContact;
|
|
|
import com.fs.qw.domain.QwIpadServer;
|
|
|
import com.fs.qw.domain.QwSopSmsLogs;
|
|
|
+import com.fs.qw.mapper.QwExternalContactMapper;
|
|
|
import com.fs.qw.mapper.QwIpadServerMapper;
|
|
|
import com.fs.qw.service.IQwSopSmsLogsService;
|
|
|
+import com.fs.system.service.ISysDictTypeService;
|
|
|
import com.fs.sop.domain.QwSopLogs;
|
|
|
import com.fs.sop.mapper.QwSopLogsMapper;
|
|
|
import com.fs.sop.service.IQwSopLogsService;
|
|
|
@@ -47,6 +51,10 @@ public class SendSmsMsg {
|
|
|
@Value("${group-no}")
|
|
|
private String groupNo;
|
|
|
|
|
|
+ private static final String DICT_TYPE_QW_EXTERNAL_CONTACT_FUNC = "sys_qw_external_contact_func";
|
|
|
+ /** 字典 dict_sort=2:是否开启根据外部联系人手机号码发送短信 */
|
|
|
+ private static final long DICT_SORT_SEND_SMS_BY_CONTACT_MOBILE = 2L;
|
|
|
+
|
|
|
private final RedisCache redisCache;
|
|
|
|
|
|
private final ISmsService smsService;
|
|
|
@@ -65,6 +73,10 @@ public class SendSmsMsg {
|
|
|
|
|
|
private final IFsCourseWatchLogService watchLogService;
|
|
|
|
|
|
+ private final ISysDictTypeService sysDictTypeService;
|
|
|
+
|
|
|
+ private final QwExternalContactMapper qwExternalContactMapper;
|
|
|
+
|
|
|
// 线程池配置
|
|
|
private static final int CORE_POOL_SIZE = 50;
|
|
|
private static final int MAX_POOL_SIZE = 150;
|
|
|
@@ -74,12 +86,18 @@ public class SendSmsMsg {
|
|
|
// 分页大小
|
|
|
private static final int PAGE_SIZE = 3000;
|
|
|
|
|
|
- // 手机号缓存
|
|
|
+ // fs_user 手机号缓存
|
|
|
private final Cache<Long, String> phoneCache = CacheBuilder.newBuilder()
|
|
|
.expireAfterWrite(1, TimeUnit.HOURS)
|
|
|
.maximumSize(100000)
|
|
|
.build();
|
|
|
|
|
|
+ // 外部联系人 contact_mobiles 缓存(取逗号分隔的第一个号码)
|
|
|
+ private final Cache<Long, String> contactMobileCache = CacheBuilder.newBuilder()
|
|
|
+ .expireAfterWrite(1, TimeUnit.HOURS)
|
|
|
+ .maximumSize(100000)
|
|
|
+ .build();
|
|
|
+
|
|
|
// 限流器:控制全局发送速率
|
|
|
private final RateLimiter rateLimiter = RateLimiter.create(500); // 每秒500条
|
|
|
|
|
|
@@ -105,7 +123,9 @@ public class SendSmsMsg {
|
|
|
QwSopLogsMapper qwSopLogsMapper,
|
|
|
IQwSopLogsService qwSopLogsService,
|
|
|
IFsCourseWatchLogService watchLogService,
|
|
|
- CompanySmsLogsMapper companySmsLogsMapper) {
|
|
|
+ CompanySmsLogsMapper companySmsLogsMapper,
|
|
|
+ ISysDictTypeService sysDictTypeService,
|
|
|
+ QwExternalContactMapper qwExternalContactMapper) {
|
|
|
this.qwIpadServerMapper = qwIpadServerMapper;
|
|
|
this.qwSopSmsLogsService = qwSopSmsLogsService;
|
|
|
this.smsService = smsService;
|
|
|
@@ -115,6 +135,8 @@ public class SendSmsMsg {
|
|
|
this.qwSopLogsService = qwSopLogsService;
|
|
|
this.watchLogService = watchLogService;
|
|
|
this.companySmsLogsMapper = companySmsLogsMapper;
|
|
|
+ this.sysDictTypeService = sysDictTypeService;
|
|
|
+ this.qwExternalContactMapper = qwExternalContactMapper;
|
|
|
}
|
|
|
|
|
|
@Scheduled(cron = "0 0 * * * ?") // 每小时执行一次
|
|
|
@@ -171,6 +193,8 @@ public class SendSmsMsg {
|
|
|
|
|
|
// 计算时间范围
|
|
|
TimeRange timeRange = calculateTimeRange();
|
|
|
+ boolean sendByContactMobile = isSendSmsByContactMobileEnabled();
|
|
|
+ log.info("sendSms: 外部联系人手机号发送开关={}", sendByContactMobile);
|
|
|
|
|
|
long lastId = 0L;
|
|
|
AtomicLong totalProcessed = new AtomicLong(0);
|
|
|
@@ -204,7 +228,7 @@ public class SendSmsMsg {
|
|
|
List<QwSopSmsLogs> serverBatch = entry.getValue();
|
|
|
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
|
|
|
try {
|
|
|
- SendResult result = processServerBatch(serverBatch,sopLogsMap);
|
|
|
+ SendResult result = processServerBatch(serverBatch, sopLogsMap, sendByContactMobile);
|
|
|
totalProcessed.addAndGet(result.success);
|
|
|
totalFailed.addAndGet(result.failed);
|
|
|
} catch (Exception e) {
|
|
|
@@ -261,15 +285,29 @@ public class SendSmsMsg {
|
|
|
return new TimeRange(startTime, endTime);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 是否开启根据外部联系人手机号码发送短信(字典 sys_qw_external_contact_func,dict_sort=2,dict_value=1)
|
|
|
+ */
|
|
|
+ private boolean isSendSmsByContactMobileEnabled() {
|
|
|
+ List<SysDictData> dictList = sysDictTypeService.selectDictDataByType(DICT_TYPE_QW_EXTERNAL_CONTACT_FUNC);
|
|
|
+ if (dictList == null || dictList.isEmpty()) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return dictList.stream()
|
|
|
+ .filter(item -> "0".equals(item.getStatus()))
|
|
|
+ .filter(item -> item.getDictSort() != null && DICT_SORT_SEND_SMS_BY_CONTACT_MOBILE == item.getDictSort())
|
|
|
+ .anyMatch(item -> "1".equals(item.getDictValue() != null ? item.getDictValue().trim() : ""));
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 处理单个server的一批短信(同步发送,避免线程爆炸)
|
|
|
*/
|
|
|
- private SendResult processServerBatch(List<QwSopSmsLogs> batch,Map<Long, QwSopLogs> sopLogsMap) {
|
|
|
+ private SendResult processServerBatch(List<QwSopSmsLogs> batch, Map<Long, QwSopLogs> sopLogsMap, boolean sendByContactMobile) {
|
|
|
int success = 0;
|
|
|
List<SendResultDetailDTO> failReasonsList = Collections.synchronizedList(new ArrayList<>());
|
|
|
List<Long> failedIds = Collections.synchronizedList(new ArrayList<>());
|
|
|
// 批量获取手机号
|
|
|
- Map<Long, String> userPhoneMap = getPhoneNumbers(batch);
|
|
|
+ Map<Long, String> phoneMap = resolvePhones(batch, sendByContactMobile);
|
|
|
|
|
|
for (QwSopSmsLogs logRecord : batch) {
|
|
|
rateLimiter.acquire();
|
|
|
@@ -301,7 +339,7 @@ public class SendSmsMsg {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
- SendResultDetailDTO detail = sendSingleSms(logRecord, userPhoneMap, redisKey);
|
|
|
+ SendResultDetailDTO detail = sendSingleSms(logRecord, phoneMap, redisKey);
|
|
|
if (detail.isSuccess()) {
|
|
|
success++;
|
|
|
} else {
|
|
|
@@ -333,9 +371,40 @@ public class SendSmsMsg {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 获取手机号映射(缓存+批量查询)
|
|
|
+ * 解析待发送记录的手机号(key 为 qw_sop_sms_logs.id)
|
|
|
+ */
|
|
|
+ private Map<Long, String> resolvePhones(List<QwSopSmsLogs> batch, boolean sendByContactMobile) {
|
|
|
+ Map<Long, String> result = new HashMap<>();
|
|
|
+ if (sendByContactMobile) {
|
|
|
+ Map<Long, String> contactPhoneMap = getContactMobiles(batch);
|
|
|
+ for (QwSopSmsLogs logRecord : batch) {
|
|
|
+ if (logRecord.getContactId() == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ String phone = contactPhoneMap.get(logRecord.getContactId());
|
|
|
+ if (phone != null) {
|
|
|
+ result.put(logRecord.getId(), phone);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ Map<Long, String> userPhoneMap = getFsUserPhones(batch);
|
|
|
+ for (QwSopSmsLogs logRecord : batch) {
|
|
|
+ if (logRecord.getFsUserId() == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ String phone = userPhoneMap.get(logRecord.getFsUserId());
|
|
|
+ if (phone != null) {
|
|
|
+ result.put(logRecord.getId(), phone);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取 fs_user 手机号映射(缓存+批量查询)
|
|
|
*/
|
|
|
- private Map<Long, String> getPhoneNumbers(List<QwSopSmsLogs> batch) {
|
|
|
+ private Map<Long, String> getFsUserPhones(List<QwSopSmsLogs> batch) {
|
|
|
// 收集所有用户ID
|
|
|
Set<Long> userIds = batch.stream()
|
|
|
.map(QwSopSmsLogs::getFsUserId)
|
|
|
@@ -369,11 +438,57 @@ public class SendSmsMsg {
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 获取外部联系人 contact_mobiles 映射(取逗号分隔的第一个号码)
|
|
|
+ */
|
|
|
+ private Map<Long, String> getContactMobiles(List<QwSopSmsLogs> batch) {
|
|
|
+ Set<Long> contactIds = batch.stream()
|
|
|
+ .map(QwSopSmsLogs::getContactId)
|
|
|
+ .filter(Objects::nonNull)
|
|
|
+ .collect(Collectors.toSet());
|
|
|
+
|
|
|
+ Map<Long, String> result = new HashMap<>();
|
|
|
+ Set<Long> missingContactIds = new HashSet<>();
|
|
|
+ for (Long contactId : contactIds) {
|
|
|
+ String phone = contactMobileCache.getIfPresent(contactId);
|
|
|
+ if (phone != null) {
|
|
|
+ result.put(contactId, phone);
|
|
|
+ } else {
|
|
|
+ missingContactIds.add(contactId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!missingContactIds.isEmpty()) {
|
|
|
+ List<QwExternalContact> contacts = qwExternalContactMapper.selectQwExternalContactByIds(new ArrayList<>(missingContactIds));
|
|
|
+ for (QwExternalContact contact : contacts) {
|
|
|
+ String phone = pickFirstContactMobile(contact.getContactMobiles());
|
|
|
+ if (phone != null) {
|
|
|
+ result.put(contact.getId(), phone);
|
|
|
+ contactMobileCache.put(contact.getId(), phone);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String pickFirstContactMobile(String contactMobiles) {
|
|
|
+ if (StringUtils.isEmpty(contactMobiles)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ for (String part : contactMobiles.split(",")) {
|
|
|
+ String mobile = part.trim();
|
|
|
+ if (!StringUtils.isEmpty(mobile)) {
|
|
|
+ return mobile;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 发送单条短信,返回是否成功
|
|
|
*/
|
|
|
- private SendResultDetailDTO sendSingleSms(QwSopSmsLogs logRecord, Map<Long, String> userPhoneMap, String redisKey) {
|
|
|
- String phone = userPhoneMap.get(logRecord.getFsUserId());
|
|
|
+ private SendResultDetailDTO sendSingleSms(QwSopSmsLogs logRecord, Map<Long, String> phoneMap, String redisKey) {
|
|
|
+ String phone = phoneMap.get(logRecord.getId());
|
|
|
String content = logRecord.getContent();
|
|
|
|
|
|
if (StringUtils.isEmpty(phone) || StringUtils.isEmpty(content)) {
|
|
|
@@ -576,6 +691,9 @@ public class SendSmsMsg {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
+ boolean sendByContactMobile = isSendSmsByContactMobileEnabled();
|
|
|
+ log.info("processSendLogs: 外部联系人手机号发送开关={}", sendByContactMobile);
|
|
|
+
|
|
|
List<Long> sopLogIds = needCheckList.stream()
|
|
|
.map(QwSopSmsLogs::getSopLogId)
|
|
|
.collect(Collectors.toList());
|
|
|
@@ -595,7 +713,7 @@ public class SendSmsMsg {
|
|
|
List<QwSopSmsLogs> serverBatch = entry.getValue();
|
|
|
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
|
|
|
try {
|
|
|
- SendResult result = processServerBatch(serverBatch, sopLogsMap);
|
|
|
+ SendResult result = processServerBatch(serverBatch, sopLogsMap, sendByContactMobile);
|
|
|
totalProcessed.addAndGet(result.success);
|
|
|
totalFailed.addAndGet(result.failed);
|
|
|
} catch (Exception e) {
|