REQ-3345: 如果聊天消息只有一个人的情况下,同步发送

This commit is contained in:
yanglin 2025-02-25 17:57:30 +08:00
parent b830369413
commit 638a6fd8fb
9 changed files with 84 additions and 40 deletions

View File

@ -32,6 +32,11 @@ public class SendChatMessageRequest extends SendMessageRequest {
@NotEmpty(message = "消息体不能为空") @NotEmpty(message = "消息体不能为空")
private Map<String, Object> messageBody; private Map<String, Object> messageBody;
/**
* 尝试同步发送
*/
private boolean trySyncSend;
/** /**
* 发送文本消息 * 发送文本消息
* *

View File

@ -27,16 +27,20 @@ import cn.axzo.im.center.api.vo.resp.MessageUpdateResponse;
import cn.axzo.im.center.api.vo.resp.UpdatableMessageSendResult; import cn.axzo.im.center.api.vo.resp.UpdatableMessageSendResult;
import cn.axzo.im.center.api.vo.resp.UserAccountResp; import cn.axzo.im.center.api.vo.resp.UserAccountResp;
import cn.axzo.im.center.common.enums.AppTypeEnum; import cn.axzo.im.center.common.enums.AppTypeEnum;
import cn.axzo.im.channel.netease.dto.MessageDispatchResponse;
import cn.axzo.im.entity.AccountRegister; import cn.axzo.im.entity.AccountRegister;
import cn.axzo.im.entity.MessageHistory;
import cn.axzo.im.entity.MessageTask; import cn.axzo.im.entity.MessageTask;
import cn.axzo.im.enums.MessageHistoryStatus; import cn.axzo.im.enums.MessageHistoryStatus;
import cn.axzo.im.enums.MessageTaskStatus; import cn.axzo.im.enums.MessageTaskStatus;
import cn.axzo.im.send.handler.CommonSendOneHandler;
import cn.axzo.im.service.AccountRegisterService; import cn.axzo.im.service.AccountRegisterService;
import cn.axzo.im.service.AccountService; import cn.axzo.im.service.AccountService;
import cn.axzo.im.service.CustomMessageService; import cn.axzo.im.service.CustomMessageService;
import cn.axzo.im.service.MessageHistoryService; import cn.axzo.im.service.MessageHistoryService;
import cn.axzo.im.service.MessageTaskService; import cn.axzo.im.service.MessageTaskService;
import cn.axzo.im.service.RobotMsgTemplateService; import cn.axzo.im.service.RobotMsgTemplateService;
import cn.axzo.im.service.impl.MessageHistoryServiceImpl;
import cn.axzo.im.updatable.UpdatableMessageManager; import cn.axzo.im.updatable.UpdatableMessageManager;
import cn.axzo.im.updatable.UpdatableMessageQueryService; import cn.axzo.im.updatable.UpdatableMessageQueryService;
import cn.axzo.im.utils.BizAssertions; import cn.axzo.im.utils.BizAssertions;
@ -72,7 +76,7 @@ import static cn.axzo.im.config.BizResultCode.SEND_IM_ACCOUNT_NOT_FOUND;
import static cn.axzo.im.config.BizResultCode.SEND_PERSSON_ERROR; import static cn.axzo.im.config.BizResultCode.SEND_PERSSON_ERROR;
/** /**
* IM消息派发相关 * IM消息派发相关
* *
* @author zuoqinbo * @author zuoqinbo
* @version V1.0 * @version V1.0
@ -93,7 +97,7 @@ public class MessageController implements MessageApi {
@Autowired @Autowired
private AccountRegisterService accountRegisterService; private AccountRegisterService accountRegisterService;
@Autowired @Autowired
private MessageHistoryService messageHistoryService; private MessageHistoryServiceImpl messageHistoryService;
@Autowired @Autowired
private CustomMessageService customMessageService; private CustomMessageService customMessageService;
@Autowired @Autowired
@ -102,6 +106,8 @@ public class MessageController implements MessageApi {
private UpdatableMessageQueryService updatableMessageQueryService; private UpdatableMessageQueryService updatableMessageQueryService;
@Autowired @Autowired
private TransactionTemplate transactionTemplate; private TransactionTemplate transactionTemplate;
@Autowired
private CommonSendOneHandler commonSendOneHandler;
@Override @Override
@ -116,7 +122,7 @@ public class MessageController implements MessageApi {
return ApiResult.ok(messageRespList); return ApiResult.ok(messageRespList);
} }
@ExceptionHandler({ RequestNotPermitted.class }) @ExceptionHandler({RequestNotPermitted.class})
@ResponseStatus(HttpStatus.TOO_MANY_REQUESTS) @ResponseStatus(HttpStatus.TOO_MANY_REQUESTS)
public ApiResult<String> handleRequestNotPermitted() { public ApiResult<String> handleRequestNotPermitted() {
return ApiResult.err("服务器资源繁忙,请求被拒绝!"); return ApiResult.err("服务器资源繁忙,请求被拒绝!");
@ -126,6 +132,7 @@ public class MessageController implements MessageApi {
/** /**
* 发送消息时只是存储在messageTask中通过xxlJob或者mq异步去处理 * 发送消息时只是存储在messageTask中通过xxlJob或者mq异步去处理
* 因为1为了提高接口响应性能2第三方接口有限流控制防止被限流后阻塞业务 * 因为1为了提高接口响应性能2第三方接口有限流控制防止被限流后阻塞业务
*
* @param sendMessageParam 发送消息请求参数 * @param sendMessageParam 发送消息请求参数
* @return * @return
*/ */
@ -244,10 +251,14 @@ public class MessageController implements MessageApi {
String sendImAccount = accountService.registerAccountIfAbsent( String sendImAccount = accountService.registerAccountIfAbsent(
sender.getPersonId(), sender.getOuId(), sender.getAppType()); sender.getPersonId(), sender.getOuId(), sender.getAppType());
BizAssertions.assertNotNull(sendImAccount, "创建账号失败"); BizAssertions.assertNotNull(sendImAccount, "创建账号失败");
boolean syncSend = request.isTrySyncSend()
&& CollectionUtils.isEmpty(request.receivePersonsOrEmpty())
&& request.getImReceiveAccounts() != null
&& request.getImReceiveAccounts().size() == 1;
MessageTask.BizData bizData = MessageTask.BizData.builder() MessageTask.BizData bizData = MessageTask.BizData.builder()
.messageBody(JSON.toJSONString(request.getMessageBody())) .messageBody(JSON.toJSONString(request.getMessageBody()))
.isSenderRobot(false) .isSenderRobot(false)
.historyCreated(true) .syncSend(syncSend)
.senderPersonId(request.determineSenderPersonId()) .senderPersonId(request.determineSenderPersonId())
.nimMessageType(request.getMessageType()) .nimMessageType(request.getMessageType())
.build(); .build();
@ -263,7 +274,7 @@ public class MessageController implements MessageApi {
.build()); .build());
} }
} }
MessageTask messageTask = transactionTemplate.execute(unused -> { Long taskId = transactionTemplate.execute(unused -> {
MessageTask task = messageTaskService.create(MessageTask.builder() MessageTask task = messageTaskService.create(MessageTask.builder()
.bizId(request.getBizId()) .bizId(request.getBizId())
.sendImAccount(sendImAccount) .sendImAccount(sendImAccount)
@ -275,12 +286,21 @@ public class MessageController implements MessageApi {
.sendPriority(SendPriority.CHAT_MESSAGE.getPriority()) .sendPriority(SendPriority.CHAT_MESSAGE.getPriority())
.apiChannel(ApiChannel.COMMON_MESSAGE) .apiChannel(ApiChannel.COMMON_MESSAGE)
.build()); .build());
task = messageTaskService.getById(task.getId()); if (syncSend) {
messageTaskService.createMessageHistory(task); task = messageTaskService.getById(task.getId());
return task; List<Long> historyIds = messageTaskService.createMessageHistory(task);
MessageHistory history = messageHistoryService.getById(historyIds.get(0));
MessageDispatchResponse response = commonSendOneHandler.send(history);
if (response.isSuccess()) {
messageHistoryService.setSendSuccess(history, response.getMsgid(), null);
} else {
log.warn("sendChatMessage, send failed, historyId={}, taskId={}, bizId={}, failReason={}",
history.getId(), history.getImMessageTaskId(), history.getBizId(), response.getDesc());
}
}
return task.getId();
}); });
//noinspection DataFlowIssue return ApiResult.ok(taskId);
return ApiResult.ok(messageTask.getId());
} }
private void ensureImAccountNotBlank(String imAccount) { private void ensureImAccountNotBlank(String imAccount) {

View File

@ -37,4 +37,5 @@ public class HistoryRecordExt {
private Long workspaceId; private Long workspaceId;
private Integer nimMessageType; private Integer nimMessageType;
private boolean syncSend;
} }

View File

@ -170,7 +170,7 @@ public class MessageTask {
private NimMessageType nimMessageType; private NimMessageType nimMessageType;
private boolean historyCreated; private boolean syncSend;
public boolean determineIsSenderRobot() { public boolean determineIsSenderRobot() {
return isSenderRobot != null && isSenderRobot; return isSenderRobot != null && isSenderRobot;

View File

@ -59,9 +59,6 @@ public class CreateMessageHistoryJob extends IJobHandler {
Page<MessageTask> page = messageTaskService.page(req); Page<MessageTask> page = messageTaskService.page(req);
if (CollectionUtils.isNotEmpty(page.getRecords())) { if (CollectionUtils.isNotEmpty(page.getRecords())) {
page.getRecords().forEach(messageTask -> { page.getRecords().forEach(messageTask -> {
MessageTask.BizData bizData = messageTask.getBizData();
if (bizData != null && bizData.isHistoryCreated())
return;
count.set(count.get() + 1); count.set(count.get() + 1);
try { try {
messageTaskService.createMessageHistory(messageTask); messageTaskService.createMessageHistory(messageTask);

View File

@ -13,6 +13,7 @@ import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
/** /**
* @author yanglin * @author yanglin
@ -27,24 +28,28 @@ public class CommonSendBatchHandler extends SendBatchHandler {
@Override @Override
public void sendAndSubmitUpdate(SendExecutor<List<MessageHistory>> executor, List<MessageHistory> histories) { public void sendAndSubmitUpdate(SendExecutor<List<MessageHistory>> executor, List<MessageHistory> histories) {
if (CollectionUtils.isEmpty(histories)) return; List<MessageHistory> effectiveHistories = histories.stream()
.filter(h -> !h.getOrCreateRecordExt().isSyncSend())
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(effectiveHistories))
return;
executor.log("batchSendMessage - request record size: {}, batchNo={}", executor.log("batchSendMessage - request record size: {}, batchNo={}",
histories.size(), histories.get(0).determineBatchNo().orElse(null)); effectiveHistories.size(), effectiveHistories.get(0).determineBatchNo().orElse(null));
MessageHistory sample = histories.get(0); MessageHistory sample = effectiveHistories.get(0);
MessageBatchDispatchRequest batchRequest = new MessageBatchDispatchRequest(); MessageBatchDispatchRequest batchRequest = new MessageBatchDispatchRequest();
batchRequest.setBody(sample.getMessageBody()); batchRequest.setBody(sample.getMessageBody());
batchRequest.setFromAccid(sample.getFromAccount()); batchRequest.setFromAccid(sample.getFromAccount());
batchRequest.setToAccids(Lists.transform(histories, MessageHistory::getToAccount)); batchRequest.setToAccids(Lists.transform(effectiveHistories, MessageHistory::getToAccount));
batchRequest.setPayload(sample.getOrCreateRecordExt().getPayload()); batchRequest.setPayload(sample.getOrCreateRecordExt().getPayload());
batchRequest.populateOption(); batchRequest.populateOption();
messageHistoryNimLogger.logAsync(histories, batchRequest); messageHistoryNimLogger.logAsync(effectiveHistories, batchRequest);
MessageBatchDispatchResponse response = imChannelProvider.dispatchBatchMessage(batchRequest); MessageBatchDispatchResponse response = imChannelProvider.dispatchBatchMessage(batchRequest);
if (response.isRateLimited()) if (response.isRateLimited())
executor.scheduleRetrySend(histories, null); executor.scheduleRetrySend(effectiveHistories, null);
else if (response.isSuccess()) else if (response.isSuccess())
executor.setBatchSendSuccess(histories, response, null); executor.setBatchSendSuccess(effectiveHistories, response, null);
else else
executor.setSendFail(histories, response.getDesc(), null); executor.setSendFail(effectiveHistories, response.getDesc(), null);
} }
@Override @Override

View File

@ -24,9 +24,21 @@ public class CommonSendOneHandler extends SendOneHandler {
@Override @Override
public void sendAndSubmitUpdate(SendExecutor<MessageHistory> executor, MessageHistory history) { public void sendAndSubmitUpdate(SendExecutor<MessageHistory> executor, MessageHistory history) {
if (history.getOrCreateRecordExt().isSyncSend())
return;
executor.log("sendMessage - historyId={}, taskId={}, bizId={}, batchNo={}", executor.log("sendMessage - historyId={}, taskId={}, bizId={}, batchNo={}",
history.getId(), history.getImMessageTaskId(), history.getId(), history.getImMessageTaskId(),
history.getBizId(), history.determineBatchNo().orElse(null)); history.getBizId(), history.determineBatchNo().orElse(null));
MessageDispatchResponse response = send(history);
if (response.isRateLimited())
executor.scheduleRetrySend(history, null);
else if (response.isSuccess())
executor.submitSetSendSuccess(history, response.getMsgid());
else
executor.submitSetSendFail(history, response.getDesc());
}
public MessageDispatchResponse send(MessageHistory history) {
MessageDispatchRequest sendRequest = new MessageDispatchRequest(); MessageDispatchRequest sendRequest = new MessageDispatchRequest();
sendRequest.setFrom(history.getFromAccount()); sendRequest.setFrom(history.getFromAccount());
sendRequest.setOpe(history.isSendToGroup() ? 1 : 0); sendRequest.setOpe(history.isSendToGroup() ? 1 : 0);
@ -40,13 +52,7 @@ public class CommonSendOneHandler extends SendOneHandler {
sendRequest.setPayload(history.getOrCreateRecordExt().getPayload()); sendRequest.setPayload(history.getOrCreateRecordExt().getPayload());
sendRequest.populateOption(); sendRequest.populateOption();
messageHistoryNimLogger.logAsync(history, sendRequest); messageHistoryNimLogger.logAsync(history, sendRequest);
MessageDispatchResponse response = imChannelProvider.dispatchMessage(sendRequest); return imChannelProvider.dispatchMessage(sendRequest);
if (response.isRateLimited())
executor.scheduleRetrySend(history, null);
else if (response.isSuccess())
executor.submitSetSendSuccess(history, response.getMsgid());
else
executor.submitSetSendFail(history, response.getDesc());
} }
@Override @Override

View File

@ -22,7 +22,7 @@ public interface MessageTaskService extends IService<MessageTask> {
Page<MessageTask> page(PageMessageTaskParam param); Page<MessageTask> page(PageMessageTaskParam param);
void createMessageHistory(MessageTask messageTask); List<Long> createMessageHistory(MessageTask messageTask);
void update(UpdateMessageTaskParam param); void update(UpdateMessageTaskParam param);

View File

@ -45,7 +45,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import java.io.Serializable; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
@ -105,7 +105,7 @@ public class MessageTaskServiceImpl extends ServiceImpl<MessageTaskMapper, Messa
@Override @Override
@Transactional @Transactional
public void createMessageHistory(MessageTask messageTask) { public List<Long> createMessageHistory(MessageTask messageTask) {
this.update(UpdateMessageTaskParam.builder() this.update(UpdateMessageTaskParam.builder()
.id(messageTask.getId()) .id(messageTask.getId())
@ -113,12 +113,13 @@ public class MessageTaskServiceImpl extends ServiceImpl<MessageTaskMapper, Messa
.build()); .build());
MessageTask.BizData bizData = messageTask.getBizData(); MessageTask.BizData bizData = messageTask.getBizData();
List<Long> historyIds;
if (bizData.isAllPerson()) { if (bizData.isAllPerson()) {
log.info("发送全员消息, taskId={}", messageTask.getId()); log.info("发送全员消息, taskId={}", messageTask.getId());
doSendAll(messageTask, bizData); historyIds = doSendAll(messageTask, bizData);
} else { } else {
log.info("发送非全员消息, taskId={}", messageTask.getId()); log.info("发送非全员消息, taskId={}", messageTask.getId());
doSendNotAll(messageTask); historyIds = doSendNotAll(messageTask);
} }
this.update(UpdateMessageTaskParam.builder() this.update(UpdateMessageTaskParam.builder()
@ -126,6 +127,7 @@ public class MessageTaskServiceImpl extends ServiceImpl<MessageTaskMapper, Messa
.action(MessageTask.ActionEnum.SUCCESS) .action(MessageTask.ActionEnum.SUCCESS)
.finishedTime(new Date()) .finishedTime(new Date())
.build()); .build());
return historyIds;
} }
@Override @Override
@ -145,9 +147,10 @@ public class MessageTaskServiceImpl extends ServiceImpl<MessageTaskMapper, Messa
this.updateById(updateMessageTask); this.updateById(updateMessageTask);
} }
private void doSendAll(MessageTask messageTask, MessageTask.BizData bizData) { private List<Long> doSendAll(MessageTask messageTask, MessageTask.BizData bizData) {
Integer pageNumber = 1; Integer pageNumber = 1;
String batchNo = UUIDUtil.uuidString(); String batchNo = UUIDUtil.uuidString();
ArrayList<Long> historyIds = new ArrayList<>();
while (true) { while (true) {
Page<AccountRegisterDTO> page = accountRegisterService.page(AccountRegisterService.PageAccountRegisterParam.builder() Page<AccountRegisterDTO> page = accountRegisterService.page(AccountRegisterService.PageAccountRegisterParam.builder()
.accountType(AccountTypeEnum.USER.getCode()) .accountType(AccountTypeEnum.USER.getCode())
@ -159,24 +162,27 @@ public class MessageTaskServiceImpl extends ServiceImpl<MessageTaskMapper, Messa
List<MessageTask.ReceivePerson> receivePersons = page.getRecords().stream() List<MessageTask.ReceivePerson> receivePersons = page.getRecords().stream()
.map(e -> MessageTask.ReceivePerson.builder().imAccount(e.getImAccount()).build()) .map(e -> MessageTask.ReceivePerson.builder().imAccount(e.getImAccount()).build())
.collect(Collectors.toList()); .collect(Collectors.toList());
saveMessageHistory(batchNo, receivePersons, messageTask, false); historyIds.addAll(saveMessageHistory(batchNo, receivePersons, messageTask, false));
} }
if (!page.hasNext()) { if (!page.hasNext()) {
break; break;
} }
} }
return historyIds;
} }
private void doSendNotAll(MessageTask messageTask) { private List<Long> doSendNotAll(MessageTask messageTask) {
int totalPersonSize = messageTask.getReceivePersons().size(); int totalPersonSize = messageTask.getReceivePersons().size();
String batchNo = UUIDUtil.uuidString(); String batchNo = UUIDUtil.uuidString();
// 防止sql过长 // 防止sql过长
List<List<MessageTask.ReceivePerson>> receivePersons = Lists.partition(messageTask.getReceivePersons(), DEFAULT_PAGE_SIZE); List<List<MessageTask.ReceivePerson>> receivePersons = Lists.partition(messageTask.getReceivePersons(), DEFAULT_PAGE_SIZE);
receivePersons.forEach(e -> saveMessageHistory(batchNo, e, messageTask, totalPersonSize <= 2)); return receivePersons.stream()
.flatMap(e -> saveMessageHistory(batchNo, e, messageTask, totalPersonSize <= 2).stream())
.collect(Collectors.toList());
} }
private void saveMessageHistory(String batchNo, List<MessageTask.ReceivePerson> receivePersons, private List<Long> saveMessageHistory(String batchNo, List<MessageTask.ReceivePerson> receivePersons,
MessageTask messageTask, boolean tryCreateAccount) { MessageTask messageTask, boolean tryCreateAccount) {
// 排除已经发送成功的记录防止重复发送 // 排除已经发送成功的记录防止重复发送
Set<String> existPersons = listExistPerson(receivePersons, messageTask); Set<String> existPersons = listExistPerson(receivePersons, messageTask);
@ -197,7 +203,7 @@ public class MessageTaskServiceImpl extends ServiceImpl<MessageTaskMapper, Messa
if (CollectionUtils.isEmpty(absentReceivePersons)) { if (CollectionUtils.isEmpty(absentReceivePersons)) {
log.info("messageTask,{}, receivePersons,{},已经存在", JSONObject.toJSONString(messageTask), log.info("messageTask,{}, receivePersons,{},已经存在", JSONObject.toJSONString(messageTask),
JSONObject.toJSONString(receivePersons)); JSONObject.toJSONString(receivePersons));
return; return Collections.emptyList();
} }
Map<String, String> accountRegisters = listAccountRegisters(absentReceivePersons); Map<String, String> accountRegisters = listAccountRegisters(absentReceivePersons);
@ -213,6 +219,7 @@ public class MessageTaskServiceImpl extends ServiceImpl<MessageTaskMapper, Messa
.map(MessageHistory::getId) .map(MessageHistory::getId)
.collect(Collectors.toList()); .collect(Collectors.toList());
updatableMessageManager.onHistoryCreated(historyIds); updatableMessageManager.onHistoryCreated(historyIds);
return historyIds;
} }
private MessageHistory resolveMessageHistory(String batchNo, private MessageHistory resolveMessageHistory(String batchNo,
@ -233,6 +240,9 @@ public class MessageTaskServiceImpl extends ServiceImpl<MessageTaskMapper, Messa
messageHistory.setBatchNo(batchNo); messageHistory.setBatchNo(batchNo);
messageHistory.setSendPriority(messageTask.getSendPriority()); messageHistory.setSendPriority(messageTask.getSendPriority());
messageHistory.setApiChannel(messageTask.getApiChannel()); messageHistory.setApiChannel(messageTask.getApiChannel());
if (messageTask.getBizData() != null) {
messageHistory.getOrCreateRecordExt().setSyncSend(messageTask.getBizData().isSyncSend());
}
if (StringUtils.isNotBlank(receivePerson.getImAccount())) { if (StringUtils.isNotBlank(receivePerson.getImAccount())) {
AccountRegisterDTO imAccount = imAccounts.get(receivePerson.getImAccount()); AccountRegisterDTO imAccount = imAccounts.get(receivePerson.getImAccount());