feat(channel/send): support all 4 binding types and validate send targets

- BindConversationAccountRequest: restrict bindingType to 1-4
- ConversationService.bindAccount: upsert binding by account+conversation+type with sortOrder and status=1
- SendService.selectSendAccount: prefer send-primary(1), fallback send-backup(2), status=1, tenant/channel match, sortOrder asc
- SendService.resolveConversationTarget/resolvePersonTarget: verify account tenant, channel type and status; ensure person identity channel type matches for single chat
This commit is contained in:
2026-07-08 17:08:38 +08:00
parent 1298a558e8
commit 6288f0a305
3 changed files with 38 additions and 5 deletions

View File

@@ -2,6 +2,7 @@ package com.sino.mci.channel.dto;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
@Data
public class BindConversationAccountRequest {
@@ -10,6 +11,7 @@ public class BindConversationAccountRequest {
private Long accountId;
@NotNull
@Range(min = 1, max = 4)
private Integer bindingType;
private Integer sortOrder;

View File

@@ -104,14 +104,11 @@ public class ConversationService {
public void bindAccount(String tenantCode, Long conversationId, BindConversationAccountRequest request) {
getConversation(tenantCode, conversationId);
ChannelAccountConversation existing = bindingMapper.selectOne(
bindingMapper.delete(
new LambdaQueryWrapper<ChannelAccountConversation>()
.eq(ChannelAccountConversation::getAccountId, request.getAccountId())
.eq(ChannelAccountConversation::getConversationId, conversationId)
.eq(ChannelAccountConversation::getBindingType, request.getBindingType()));
if (existing != null) {
throw new BusinessException("该账号已绑定此会话的相同类型");
}
ChannelAccountConversation binding = new ChannelAccountConversation();
binding.setAccountId(request.getAccountId());

View File

@@ -9,7 +9,9 @@ import com.sino.mci.channel.common.dto.SendMessageResult;
import com.sino.mci.channel.common.model.ChannelType;
import com.sino.mci.channel.common.spi.ChannelAdapter;
import com.sino.mci.channel.entity.ChannelAccount;
import com.sino.mci.channel.entity.ChannelAccountConversation;
import com.sino.mci.channel.entity.Conversation;
import com.sino.mci.channel.repository.ChannelAccountConversationMapper;
import com.sino.mci.channel.repository.ChannelAccountMapper;
import com.sino.mci.channel.repository.ConversationMapper;
import com.sino.mci.channel.service.AccountSelector;
@@ -80,6 +82,7 @@ public class SendService {
private final SendRecordMapper sendRecordMapper;
private final ConversationMapper conversationMapper;
private final ChannelAccountMapper accountMapper;
private final ChannelAccountConversationMapper channelAccountConversationMapper;
private final ChannelIdentityMapper channelIdentityMapper;
private final PersonMapper personMapper;
private final TagMapper tagMapper;
@@ -332,6 +335,12 @@ public class SendService {
if (accountId == null) {
throw new BusinessException("会话未配置发送账号: " + conversationId);
}
ChannelAccount account = accountMapper.selectById(accountId);
if (account == null || !tenantCode.equals(account.getTenantCode())
|| !channelType.name().equalsIgnoreCase(account.getChannelType())
|| account.getStatus() == null || account.getStatus() != STATUS_ENABLED) {
throw new BusinessException("会话没有可用的发送账号: " + conversationId);
}
SendTarget target = new SendTarget();
target.setConversationId(conversationId);
target.setPersonId(null);
@@ -356,6 +365,14 @@ public class SendService {
if (accountId == null) {
throw new BusinessException("租户未配置可用发送账号: channel=" + channelType.name().toLowerCase());
}
ChannelAccount account = accountMapper.selectById(accountId);
if (account == null || !tenantCode.equals(account.getTenantCode())
|| account.getStatus() == null || account.getStatus() != STATUS_ENABLED) {
throw new BusinessException("租户没有可用的发送账号: channel=" + channelType.name().toLowerCase());
}
if (!channelType.name().equalsIgnoreCase(account.getChannelType())) {
throw new BusinessException("单聊发送账号渠道类型与目标身份不一致");
}
SendTarget target = new SendTarget();
target.setConversationId(null);
target.setPersonId(personId);
@@ -675,7 +692,24 @@ public class SendService {
}
private Long selectSendAccount(String tenantCode, Long conversationId, ChannelType channelType, Map<String, Object> channelStrategy) {
return accountSelector.selectSendAccount(tenantCode, conversationId, channelType, extractStrategy(channelStrategy));
List<Integer> bindingTypes = List.of(1, 2);
for (Integer bindingType : bindingTypes) {
List<ChannelAccountConversation> bindings = channelAccountConversationMapper.selectList(
new LambdaQueryWrapper<ChannelAccountConversation>()
.eq(ChannelAccountConversation::getConversationId, conversationId)
.eq(ChannelAccountConversation::getBindingType, bindingType)
.eq(ChannelAccountConversation::getStatus, STATUS_ENABLED)
.orderByAsc(ChannelAccountConversation::getSortOrder));
for (ChannelAccountConversation binding : bindings) {
ChannelAccount account = accountMapper.selectById(binding.getAccountId());
if (account != null && tenantCode.equals(account.getTenantCode())
&& channelType.name().equalsIgnoreCase(account.getChannelType())
&& account.getStatus() != null && account.getStatus() == STATUS_ENABLED) {
return account.getId();
}
}
}
return null;
}
private Long selectDefaultAccount(String tenantCode, ChannelType channelType) {