fix(send-routing): address re-review issues

- Set conversationScene=EXTERNAL in SendEndToEndTest and align account to WECHAT_PERSONAL
- Replace literal account type code with AccountType.WECHAT_PERSONAL.getCode()
- Use replaceFirst for webhook URL placeholder in WeComBotSender
- Remove unused selectDefaultAccount overload
- Hide scene selector for single-chat conversations in admin-web
This commit is contained in:
2026-07-15 12:08:56 +08:00
parent 0a326e7ff9
commit 168cd2de11
5 changed files with 31 additions and 17 deletions

View File

@@ -30,7 +30,7 @@
<el-table-column prop="channelType" label="渠道" /> <el-table-column prop="channelType" label="渠道" />
<el-table-column prop="conversationScene" label="场景" width="100"> <el-table-column prop="conversationScene" label="场景" width="100">
<template #default="{ row }"> <template #default="{ row }">
{{ formatConversationScene(row.conversationScene) }} {{ formatConversationScene(row.conversationScene, row.conversationType) }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="channelConversationId" label="渠道会话ID" show-overflow-tooltip /> <el-table-column prop="channelConversationId" label="渠道会话ID" show-overflow-tooltip />
@@ -76,7 +76,7 @@
<el-form-item label="渠道"> <el-form-item label="渠道">
<el-input v-model="dialogForm.channelType" placeholder="wecom" /> <el-input v-model="dialogForm.channelType" placeholder="wecom" />
</el-form-item> </el-form-item>
<el-form-item label="场景"> <el-form-item v-if="dialogForm.conversationType === 2" label="场景">
<el-select v-model="dialogForm.conversationScene" placeholder="选择场景" style="width: 100%"> <el-select v-model="dialogForm.conversationScene" placeholder="选择场景" style="width: 100%">
<el-option label="内部群" :value="1" /> <el-option label="内部群" :value="1" />
<el-option label="外部群" :value="2" /> <el-option label="外部群" :value="2" />
@@ -125,7 +125,7 @@
<el-form-item label="渠道"> <el-form-item label="渠道">
<el-input v-model="dialogForm.channelType" placeholder="wecom" /> <el-input v-model="dialogForm.channelType" placeholder="wecom" />
</el-form-item> </el-form-item>
<el-form-item label="场景"> <el-form-item v-if="dialogForm.conversationType === 2" label="场景">
<el-select v-model="dialogForm.conversationScene" placeholder="选择场景" style="width: 100%"> <el-select v-model="dialogForm.conversationScene" placeholder="选择场景" style="width: 100%">
<el-option label="内部群" :value="1" /> <el-option label="内部群" :value="1" />
<el-option label="外部群" :value="2" /> <el-option label="外部群" :value="2" />
@@ -326,6 +326,12 @@ watch([searchQuery, typeFilter], () => {
currentPage.value = 1 currentPage.value = 1
}) })
watch(() => dialogForm.conversationType, (val) => {
if (val === 1) {
dialogForm.conversationScene = undefined
}
})
function resetDialogForm() { function resetDialogForm() {
dialogForm.conversationType = 2 dialogForm.conversationType = 2
dialogForm.channelType = 'wecom' dialogForm.channelType = 'wecom'
@@ -401,7 +407,8 @@ function formatConversationType(type: number | undefined) {
return type ?? '-' return type ?? '-'
} }
function formatConversationScene(scene: number | undefined) { function formatConversationScene(scene: number | undefined, type?: number) {
if (type === 1) return '-'
if (scene === 1) return '内部群' if (scene === 1) return '内部群'
if (scene === 2) return '外部群' if (scene === 2) return '外部群'
return scene ?? '-' return scene ?? '-'

View File

@@ -91,7 +91,7 @@ public class WeComBotSender {
String customUrl = getString(extra, "webhook_url"); String customUrl = getString(extra, "webhook_url");
if (customUrl != null && !customUrl.isBlank()) { if (customUrl != null && !customUrl.isBlank()) {
if (customUrl.contains("%s")) { if (customUrl.contains("%s")) {
return customUrl.replace("%s", key); return customUrl.replaceFirst("%s", key);
} }
return customUrl; return customUrl;
} }

View File

@@ -742,10 +742,6 @@ public class SendService {
return CollectionUtils.isEmpty(accounts) ? null : accounts.get(0); return CollectionUtils.isEmpty(accounts) ? null : accounts.get(0);
} }
private Long selectDefaultAccount(String tenantCode, ChannelType channelType) {
return selectDefaultAccount(tenantCode, channelType, null);
}
private Long selectDefaultAccount(String tenantCode, ChannelType channelType, Set<Integer> accountTypes) { private Long selectDefaultAccount(String tenantCode, ChannelType channelType, Set<Integer> accountTypes) {
List<ChannelAccount> accounts = accountMapper.selectList( List<ChannelAccount> accounts = accountMapper.selectList(
new LambdaQueryWrapper<ChannelAccount>() new LambdaQueryWrapper<ChannelAccount>()

View File

@@ -52,10 +52,11 @@ class SendEndToEndTest {
.andReturn().getResponse().getContentAsString(); .andReturn().getResponse().getContentAsString();
Long subjectId = ((Number) com.jayway.jsonpath.JsonPath.read(subjectResponse, "$.data.id")).longValue(); Long subjectId = ((Number) com.jayway.jsonpath.JsonPath.read(subjectResponse, "$.data.id")).longValue();
// 2. 创建渠道账号 // 2. 创建外部群发送账号pywechat 个人号)
String accountCode = "wxid-e2e-" + UUID.randomUUID().toString().substring(0, 8);
String accountPayload = String.format( String accountPayload = String.format(
"{\"accountType\":1,\"channelType\":\"wecom\",\"subjectId\":%d,\"accountId\":\"ZhangSan\",\"accountName\":\"张三\"}", "{\"accountType\":3,\"channelType\":\"wechat_personal\",\"subjectId\":%d,\"accountId\":\"%s\",\"accountName\":\"张三\"}",
subjectId); subjectId, accountCode);
String accountResponse = mockMvc.perform(post("/msg-platform/api/v1/channel-account") String accountResponse = mockMvc.perform(post("/msg-platform/api/v1/channel-account")
.contextPath("/msg-platform") .contextPath("/msg-platform")
.header("Authorization", authToken) .header("Authorization", authToken)
@@ -67,10 +68,20 @@ class SendEndToEndTest {
.andReturn().getResponse().getContentAsString(); .andReturn().getResponse().getContentAsString();
Long accountId = ((Number) com.jayway.jsonpath.JsonPath.read(accountResponse, "$.data.id")).longValue(); Long accountId = ((Number) com.jayway.jsonpath.JsonPath.read(accountResponse, "$.data.id")).longValue();
// 3. 创建会话 // 个人号需要心跳才能被路由视为可用
mockMvc.perform(post("/msg-platform/api/v1/channel-account/heartbeat")
.contextPath("/msg-platform")
.header("Authorization", authToken)
.header("X-Tenant-Code", tenantCode)
.contentType(MediaType.APPLICATION_JSON)
.content(String.format("{\"accountId\":\"%s\",\"riskLevel\":1}", accountCode)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(0));
// 3. 创建外部群会话
String channelConversationId = "room-" + UUID.randomUUID().toString().substring(0, 8); String channelConversationId = "room-" + UUID.randomUUID().toString().substring(0, 8);
String conversationPayload = String.format( String conversationPayload = String.format(
"{\"conversationType\":2,\"channelType\":\"wecom\",\"channelConversationId\":\"%s\",\"conversationName\":\"E2E群\",\"subjectId\":%d}", "{\"conversationType\":2,\"channelType\":\"wechat_personal\",\"conversationScene\":2,\"channelConversationId\":\"%s\",\"conversationName\":\"E2E群\",\"subjectId\":%d}",
channelConversationId, subjectId); channelConversationId, subjectId);
String conversationResponse = mockMvc.perform(post("/msg-platform/api/v1/conversation") String conversationResponse = mockMvc.perform(post("/msg-platform/api/v1/conversation")
.contextPath("/msg-platform") .contextPath("/msg-platform")
@@ -121,7 +132,7 @@ class SendEndToEndTest {
.andExpect(jsonPath("$.data.length()").value(1)) .andExpect(jsonPath("$.data.length()").value(1))
.andExpect(jsonPath("$.data[0].taskId").value(taskId)) .andExpect(jsonPath("$.data[0].taskId").value(taskId))
.andExpect(jsonPath("$.data[0].sendStatus").value(2)) .andExpect(jsonPath("$.data[0].sendStatus").value(2))
.andExpect(jsonPath("$.data[0].channelType").value("wecom")) .andExpect(jsonPath("$.data[0].channelType").value("wechat_personal"))
.andExpect(jsonPath("$.data[0].conversationId").value(conversationId)) .andExpect(jsonPath("$.data[0].conversationId").value(conversationId))
.andExpect(jsonPath("$.data[0].accountId").value(accountId)) .andExpect(jsonPath("$.data[0].accountId").value(accountId))
.andExpect(jsonPath("$.data[0].contentType").value("text")) .andExpect(jsonPath("$.data[0].contentType").value("text"))

View File

@@ -115,9 +115,9 @@ class SendServiceFailoverTest {
verify(adapter, times(2)).sendMessage(requestCaptor.capture()); verify(adapter, times(2)).sendMessage(requestCaptor.capture());
List<SendMessageRequest> requests = requestCaptor.getAllValues(); List<SendMessageRequest> requests = requestCaptor.getAllValues();
assertEquals("wxid-primary", requests.get(0).getExtra().get("account_id")); assertEquals("wxid-primary", requests.get(0).getExtra().get("account_id"));
assertEquals(Integer.valueOf(3), requests.get(0).getExtra().get("account_type")); assertEquals(Integer.valueOf(AccountType.WECHAT_PERSONAL.getCode()), requests.get(0).getExtra().get("account_type"));
assertEquals("wxid-backup", requests.get(1).getExtra().get("account_id")); assertEquals("wxid-backup", requests.get(1).getExtra().get("account_id"));
assertEquals(Integer.valueOf(3), requests.get(1).getExtra().get("account_type")); assertEquals(Integer.valueOf(AccountType.WECHAT_PERSONAL.getCode()), requests.get(1).getExtra().get("account_type"));
ArgumentCaptor<ChannelAccount> accountCaptor = ArgumentCaptor.forClass(ChannelAccount.class); ArgumentCaptor<ChannelAccount> accountCaptor = ArgumentCaptor.forClass(ChannelAccount.class);
verify(accountMapper, times(1)).updateById(accountCaptor.capture()); verify(accountMapper, times(1)).updateById(accountCaptor.capture());