fix: protect business fields during wecom sync

This commit is contained in:
2026-07-08 15:07:58 +08:00
parent f350313395
commit 7ec77732e0
3 changed files with 231 additions and 0 deletions

View File

@@ -234,6 +234,12 @@ public class ChannelAccountService {
person = personMapper.selectById(identity.getPersonId());
person.setPersonName(contact.getName());
person.setPhone(contact.getMobile());
Person existing = personMapper.selectById(person.getId());
if (existing != null) {
person.setInstitutionId(existing.getInstitutionId());
person.setSupplierId(existing.getSupplierId());
person.setPersonType(existing.getPersonType());
}
personMapper.updateById(person);
identity.setChannelUserName(contact.getName());
@@ -273,6 +279,13 @@ public class ChannelAccountService {
conversation.setConversationName(conv.getChatName());
conversation.setOwnerAccountId(account.getId());
conversation.setSubjectId(subjectId);
Conversation existing = conversationMapper.selectById(conversation.getId());
if (existing != null) {
conversation.setInstitutionId(existing.getInstitutionId());
conversation.setSupplierId(existing.getSupplierId());
conversation.setContractId(existing.getContractId());
conversation.setChannelCode(existing.getChannelCode());
}
conversationMapper.updateById(conversation);
return conversation;
}

View File

@@ -179,6 +179,84 @@ class WeComSyncTest {
assertThat(receiveMaster).isNotNull();
}
@Test
void shouldPreservePersonBusinessFieldsDuringContactSync() throws Exception {
String authToken = createTenantAndLogin(mockMvc, TENANT_CODE, "企微同步租户");
ChannelAccount account = createWeComAccount();
Person person = new Person();
person.setTenantCode(TENANT_CODE);
person.setPersonCode("user001");
person.setPersonName("原始姓名");
person.setPhone("13800138001");
person.setPersonType(1);
person.setInstitutionId(100L);
person.setSupplierId(200L);
person.setStatus(1);
personMapper.insert(person);
ChannelIdentity identity = new ChannelIdentity();
identity.setTenantCode(TENANT_CODE);
identity.setPersonId(person.getId());
identity.setChannelType(CHANNEL_TYPE);
identity.setChannelUserId("user001");
identity.setChannelUserName("原始姓名");
identity.setSubjectId(account.getSubjectId());
identity.setStatus(1);
channelIdentityMapper.insert(identity);
mockMvc.perform(post("/msg-platform/api/v1/channel-account/{account_id}/sync-contacts", account.getId())
.contextPath("/msg-platform")
.header("Authorization", authToken)
.header("X-Tenant-Code", TENANT_CODE)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(0));
Person updated = personMapper.selectById(person.getId());
assertThat(updated.getPersonName()).isEqualTo("张三");
assertThat(updated.getInstitutionId()).isEqualTo(100L);
assertThat(updated.getSupplierId()).isEqualTo(200L);
assertThat(updated.getPersonType()).isEqualTo(1);
}
@Test
void shouldPreserveConversationBusinessFieldsDuringConversationSync() throws Exception {
String authToken = createTenantAndLogin(mockMvc, TENANT_CODE, "企微同步租户");
ChannelAccount account = createWeComAccount();
Conversation conversation = new Conversation();
conversation.setTenantCode(TENANT_CODE);
conversation.setConversationType(2);
conversation.setChannelType(CHANNEL_TYPE);
conversation.setChannelConversationId("chat001");
conversation.setOwnerAccountId(account.getId());
conversation.setSubjectId(account.getSubjectId());
conversation.setConversationName("原始群名");
conversation.setInstitutionId(100L);
conversation.setSupplierId(200L);
conversation.setContractId(300L);
conversation.setChannelCode("ORIGINAL");
conversation.setCreateSource(1);
conversation.setStatus(1);
conversationMapper.insert(conversation);
mockMvc.perform(post("/msg-platform/api/v1/channel-account/{account_id}/sync-conversations", account.getId())
.contextPath("/msg-platform")
.header("Authorization", authToken)
.header("X-Tenant-Code", TENANT_CODE)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(0));
Conversation updated = conversationMapper.selectById(conversation.getId());
assertThat(updated.getConversationName()).isEqualTo("测试客户群");
assertThat(updated.getInstitutionId()).isEqualTo(100L);
assertThat(updated.getSupplierId()).isEqualTo(200L);
assertThat(updated.getContractId()).isEqualTo(300L);
assertThat(updated.getChannelCode()).isEqualTo("ORIGINAL");
}
private ChannelAccount createWeComAccount() {
ChannelSubject subject = new ChannelSubject();
subject.setTenantCode(TENANT_CODE);

View File

@@ -0,0 +1,140 @@
#!/bin/bash
set -e
# Regression script for Task 1: business fields must survive WeCom sync.
#
# Pre-requisites:
# - mci-server is running (default http://localhost/msg-platform)
# - WeCom sync client is mocked or the real client returns a conversation
# whose channelConversationId matches the one created below.
# - The conversation API currently does NOT expose contractId/institutionId/
# supplierId/channelCode, so after creating the conversation via API you
# must set contract_id = 100 directly in the DB (or extend the DTO).
# - jq and curl are installed.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR/../.."
BASE_URL="${BASE_URL:-http://localhost/msg-platform}"
command -v curl >/dev/null 2>&1 || { echo "curl is required but not installed."; exit 1; }
command -v jq >/dev/null 2>&1 || { echo "jq is required but not installed."; exit 1; }
TENANT_CODE="wecom-boundary-$(date +%s)"
make_request() {
local method=$1
local url=$2
local payload=$3
shift 3
local headers=("$@")
if [ "$method" = "GET" ]; then
curl -s -S -X GET "${headers[@]}" "$url"
elif [ -n "$payload" ]; then
curl -s -S -X "$method" "${headers[@]}" -H "Content-Type: application/json" -d "$payload" "$url"
else
curl -s -S -X "$method" "${headers[@]}" -H "Content-Type: application/json" "$url"
fi
}
assert_code_zero() {
local name=$1
local response=$2
local code
code=$(echo "$response" | jq -r '.code // .errcode // empty')
if [ "$code" = "0" ]; then
echo "[PASS] $name"
return 0
else
echo "[FAIL] $name (code=$code)"
echo " response: $response"
exit 1
fi
}
# 1. Create tenant
echo "==> 1. Create tenant"
TENANT_RESPONSE=$(make_request POST "$BASE_URL/api/v1/account/tenant" \
"{\"tenantCode\":\"$TENANT_CODE\",\"tenantName\":\"WeCom Boundary Test\"}")
assert_code_zero "Create tenant" "$TENANT_RESPONSE"
# 2. Create admin user and login
echo "==> 2. Create admin user and login"
make_request POST "$BASE_URL/api/v1/account/user" \
'{"username":"admin","password":"admin123","realName":"Admin","roleCodes":["tenant_admin"]}' \
-H "X-Tenant-Code: $TENANT_CODE" >/dev/null
LOGIN_RESPONSE=$(make_request POST "$BASE_URL/api/v1/account/login" \
'{"username":"admin","password":"admin123"}' \
-H "X-Tenant-Code: $TENANT_CODE")
assert_code_zero "Login" "$LOGIN_RESPONSE"
TOKEN=$(echo "$LOGIN_RESPONSE" | jq -r '.data.token')
AUTH_HEADER="Authorization: Bearer $TOKEN"
COMMON_HEADERS=(-H "$AUTH_HEADER" -H "X-Tenant-Code: $TENANT_CODE")
# 3. Create channel subject (corp credentials are required for sync)
echo "==> 3. Create channel subject"
SUBJECT_RESPONSE=$(make_request POST "$BASE_URL/api/v1/channel-subject" \
'{"channelType":"wecom","subjectCode":"sino","subjectName":"Sino Boundary","corpId":"ww-boundary","corpSecret":"secret","agentId":"1000002"}' \
"${COMMON_HEADERS[@]}")
assert_code_zero "Create channel subject" "$SUBJECT_RESPONSE"
SUBJECT_ID=$(echo "$SUBJECT_RESPONSE" | jq -r '.data.id')
# 4. Create channel account
echo "==> 4. Create channel account"
ACCOUNT_RESPONSE=$(make_request POST "$BASE_URL/api/v1/channel-account" \
"{\"accountType\":1,\"channelType\":\"wecom\",\"accountId\":\"boundary-account\",\"accountName\":\"Boundary Account\",\"subjectId\":$SUBJECT_ID}" \
"${COMMON_HEADERS[@]}")
assert_code_zero "Create channel account" "$ACCOUNT_RESPONSE"
ACCOUNT_ID=$(echo "$ACCOUNT_RESPONSE" | jq -r '.data.id')
# 5. Create conversation via API
echo "==> 5. Create conversation"
CONVERSATION_RESPONSE=$(make_request POST "$BASE_URL/api/v1/conversation" \
'{"conversationType":2,"channelType":"wecom","channelConversationId":"chat-boundary-001","conversationName":"Boundary Group"}' \
"${COMMON_HEADERS[@]}")
assert_code_zero "Create conversation" "$CONVERSATION_RESPONSE"
CONVERSATION_ID=$(echo "$CONVERSATION_RESPONSE" | jq -r '.data.id')
# 6. Set business fields in DB (contract_id=100).
# The public API does not expose these fields yet, so this step is performed
# directly in the database. Example:
#
# UPDATE mci_conversation
# SET contract_id = 100,
# institution_id = 1000,
# supplier_id = 2000,
# channel_code = 'ORIGINAL'
# WHERE id = <CONVERSATION_ID>;
#
echo "==> 6. Set contractId=100 in DB for conversation $CONVERSATION_ID"
echo " (execute the SQL shown in the script comments, then press any key)"
read -r -n 1 -s || true
# 7. Call sync-conversations
echo "==> 7. Call sync-conversations"
SYNC_RESPONSE=$(make_request POST "$BASE_URL/api/v1/channel-account/$ACCOUNT_ID/sync-conversations" \
'' "${COMMON_HEADERS[@]}")
assert_code_zero "Sync conversations" "$SYNC_RESPONSE"
# 8. Query conversation and assert contractId is still 100
echo "==> 8. Query conversation and assert business fields"
QUERY_RESPONSE=$(make_request GET "$BASE_URL/api/v1/conversation/$CONVERSATION_ID" \
'' "${COMMON_HEADERS[@]}")
assert_code_zero "Query conversation" "$QUERY_RESPONSE"
CONTRACT_ID=$(echo "$QUERY_RESPONSE" | jq -r '.data.contractId // empty')
if [ "$CONTRACT_ID" = "100" ]; then
echo "[PASS] contractId preserved (100) after sync"
else
echo "[FAIL] contractId was overwritten or not set (got '$CONTRACT_ID')"
echo " response: $QUERY_RESPONSE"
exit 1
fi
echo ""
echo "========================="
echo "All regression checks passed."
echo "========================="