fix: 渠道账号群聊同步缓存 key 及成员名称兜底;统一 Docker/Maven 项目约定
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package com.sino.mci.channel.wecom.sync;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
@@ -10,10 +11,11 @@ import java.util.List;
|
||||
* <p>不发起真实企微 API 调用,仅返回固定样例数据,供开发与测试使用。</p>
|
||||
*/
|
||||
@Component
|
||||
@ConditionalOnProperty(name = "mci.wecom.sync.mock", havingValue = "true")
|
||||
public class MockWeComSyncClient implements WeComSyncClient {
|
||||
|
||||
@Override
|
||||
public List<WeComContact> syncContacts(WeComSyncConfig config) {
|
||||
public List<WeComContact> syncEnterpriseContacts(WeComSyncConfig config) {
|
||||
return List.of(
|
||||
WeComContact.builder()
|
||||
.userId("user001")
|
||||
@@ -21,7 +23,13 @@ public class MockWeComSyncClient implements WeComSyncClient {
|
||||
.mobile("13800138001")
|
||||
.type(WeComContactType.INTERNAL)
|
||||
.departmentIds(List.of(1L, 2L))
|
||||
.build(),
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<WeComContact> syncAccountContacts(WeComSyncConfig config, String userId) {
|
||||
return List.of(
|
||||
WeComContact.builder()
|
||||
.userId("wmexternal001")
|
||||
.name("客户李四")
|
||||
@@ -32,15 +40,15 @@ public class MockWeComSyncClient implements WeComSyncClient {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<WeComConversation> syncConversations(WeComSyncConfig config) {
|
||||
public List<WeComConversation> syncAccountConversations(WeComSyncConfig config, String userId) {
|
||||
return List.of(
|
||||
WeComConversation.builder()
|
||||
.chatId("chat001")
|
||||
.chatName("测试客户群")
|
||||
.ownerUserId("user001")
|
||||
.ownerUserId(userId)
|
||||
.memberList(List.of(
|
||||
WeComConversationMember.builder()
|
||||
.userId("user001")
|
||||
.userId(userId)
|
||||
.name("张三")
|
||||
.build(),
|
||||
WeComConversationMember.builder()
|
||||
|
||||
@@ -0,0 +1,258 @@
|
||||
package com.sino.mci.channel.wecom.sync;
|
||||
|
||||
import com.sino.mci.channel.wecom.support.WeComServiceFactory;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import me.chanjar.weixin.common.error.WxErrorException;
|
||||
import me.chanjar.weixin.cp.api.WxCpService;
|
||||
import me.chanjar.weixin.cp.bean.WxCpDepart;
|
||||
import me.chanjar.weixin.cp.bean.WxCpUser;
|
||||
import me.chanjar.weixin.cp.bean.external.WxCpUserExternalGroupChatInfo;
|
||||
import me.chanjar.weixin.cp.bean.external.WxCpUserExternalGroupChatList;
|
||||
import me.chanjar.weixin.cp.bean.external.contact.WxCpExternalContactInfo;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 企业微信通讯录与群聊真实同步客户端。
|
||||
*
|
||||
* <p>基于 {@link WxCpService} 调用企微开放接口,拉取企业内部成员、外部联系人及客户群。</p>
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@ConditionalOnProperty(name = "mci.wecom.sync.mock", havingValue = "false", matchIfMissing = true)
|
||||
public class RealWeComSyncClient implements WeComSyncClient {
|
||||
|
||||
private final WeComServiceFactory serviceFactory;
|
||||
|
||||
@Override
|
||||
public List<WeComContact> syncEnterpriseContacts(WeComSyncConfig config) {
|
||||
WxCpService service = resolveService(config);
|
||||
if (service == null) {
|
||||
log.warn("[WeComSync] 无法创建 WxCpService,跳过企业通讯录同步");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<WeComContact> contacts = new ArrayList<>();
|
||||
contacts.addAll(syncInternalUsers(service, config));
|
||||
contacts.addAll(syncExternalContacts(service, config));
|
||||
log.info("[WeComSync] 同步企业通讯录完成,共 {} 条", contacts.size());
|
||||
return contacts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<WeComContact> syncAccountContacts(WeComSyncConfig config, String userId) {
|
||||
WxCpService service = resolveService(config);
|
||||
if (service == null) {
|
||||
log.warn("[WeComSync] 无法创建 WxCpService,跳过账号客户同步");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<WeComContact> contacts = syncExternalContactsForUser(service, config, userId);
|
||||
log.info("[WeComSync] 同步账号 {} 的客户完成,共 {} 条", userId, contacts.size());
|
||||
return contacts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<WeComConversation> syncAccountConversations(WeComSyncConfig config, String userId) {
|
||||
WxCpService service = resolveService(config);
|
||||
if (service == null) {
|
||||
log.warn("[WeComSync] 无法创建 WxCpService,跳过账号群聊同步");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<WeComConversation> conversations = new ArrayList<>();
|
||||
try {
|
||||
String cursor = "";
|
||||
do {
|
||||
WxCpUserExternalGroupChatList result = service.getExternalContactService()
|
||||
.listGroupChat(1000, cursor, 0, new String[]{userId});
|
||||
if (result == null || result.getGroupChatList() == null) {
|
||||
break;
|
||||
}
|
||||
for (WxCpUserExternalGroupChatList.ChatStatus chat : result.getGroupChatList()) {
|
||||
WeComConversation conversation = fetchGroupChatDetail(service, chat.getChatId());
|
||||
if (conversation != null) {
|
||||
conversations.add(conversation);
|
||||
}
|
||||
}
|
||||
cursor = result.getNextCursor();
|
||||
} while (cursor != null && !cursor.isEmpty());
|
||||
} catch (WxErrorException e) {
|
||||
log.warn("[WeComSync] 拉取账号 {} 的客户群列表失败,可能应用缺少客户联系权限: {}", userId, e.getMessage());
|
||||
} catch (Exception e) {
|
||||
log.warn("[WeComSync] 拉取账号 {} 的客户群列表异常", userId, e);
|
||||
}
|
||||
log.info("[WeComSync] 同步账号 {} 的群聊完成,共 {} 条", userId, conversations.size());
|
||||
return conversations;
|
||||
}
|
||||
|
||||
private WxCpService resolveService(WeComSyncConfig config) {
|
||||
if (config == null || config.getCorpId() == null || config.getAgentId() == null || config.getSecret() == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return serviceFactory.getOrCreateService(
|
||||
config.getCorpId(), Integer.valueOf(config.getAgentId()), config.getSecret());
|
||||
} catch (Exception e) {
|
||||
log.warn("[WeComSync] 创建 WxCpService 失败", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private List<WeComContact> syncInternalUsers(WxCpService service, WeComSyncConfig config) {
|
||||
List<WeComContact> contacts = new ArrayList<>();
|
||||
try {
|
||||
List<WxCpDepart> departments = service.getDepartmentService().list(null);
|
||||
if (departments == null || departments.isEmpty()) {
|
||||
return contacts;
|
||||
}
|
||||
for (WxCpDepart dept : departments) {
|
||||
try {
|
||||
List<WxCpUser> users = service.getUserService().listByDepartment(dept.getId(), true, 0);
|
||||
if (users == null) {
|
||||
continue;
|
||||
}
|
||||
for (WxCpUser user : users) {
|
||||
contacts.add(WeComContact.builder()
|
||||
.userId(user.getUserId())
|
||||
.name(user.getName())
|
||||
.mobile(user.getMobile())
|
||||
.type(WeComContactType.INTERNAL)
|
||||
.departmentIds(user.getDepartIds() == null
|
||||
? Collections.emptyList()
|
||||
: Arrays.stream(user.getDepartIds()).collect(Collectors.toList()))
|
||||
.build());
|
||||
}
|
||||
} catch (WxErrorException e) {
|
||||
log.warn("[WeComSync] 获取部门 {} 成员失败: {}", dept.getId(), e.getMessage());
|
||||
}
|
||||
}
|
||||
} catch (WxErrorException e) {
|
||||
log.warn("[WeComSync] 拉取部门列表失败,可能应用缺少通讯录权限: {}", e.getMessage());
|
||||
} catch (Exception e) {
|
||||
log.warn("[WeComSync] 拉取内部成员异常", e);
|
||||
}
|
||||
return contacts;
|
||||
}
|
||||
|
||||
private List<WeComContact> syncExternalContacts(WxCpService service, WeComSyncConfig config) {
|
||||
List<WeComContact> contacts = new ArrayList<>();
|
||||
try {
|
||||
List<String> followUsers = service.getExternalContactService().listFollowers();
|
||||
if (followUsers == null || followUsers.isEmpty()) {
|
||||
return contacts;
|
||||
}
|
||||
for (String userId : followUsers) {
|
||||
contacts.addAll(syncExternalContactsForUser(service, config, userId));
|
||||
}
|
||||
} catch (WxErrorException e) {
|
||||
log.warn("[WeComSync] 拉取客户联系成员失败,可能应用缺少客户联系权限: {}", e.getMessage());
|
||||
} catch (Exception e) {
|
||||
log.warn("[WeComSync] 拉取外部联系人异常", e);
|
||||
}
|
||||
return contacts;
|
||||
}
|
||||
|
||||
private List<WeComContact> syncExternalContactsForUser(WxCpService service, WeComSyncConfig config, String userId) {
|
||||
List<WeComContact> contacts = new ArrayList<>();
|
||||
try {
|
||||
List<String> externalUserIds = service.getExternalContactService().listExternalContacts(userId);
|
||||
if (externalUserIds == null) {
|
||||
return contacts;
|
||||
}
|
||||
for (String externalUserId : externalUserIds) {
|
||||
try {
|
||||
WxCpExternalContactInfo info = service.getExternalContactService()
|
||||
.getContactDetail(externalUserId, null);
|
||||
if (info == null || info.getExternalContact() == null) {
|
||||
continue;
|
||||
}
|
||||
contacts.add(WeComContact.builder()
|
||||
.userId(externalUserId)
|
||||
.name(info.getExternalContact().getName())
|
||||
.mobile(null)
|
||||
.type(WeComContactType.EXTERNAL)
|
||||
.departmentIds(Collections.emptyList())
|
||||
.build());
|
||||
} catch (WxErrorException e) {
|
||||
log.warn("[WeComSync] 获取外部联系人 {} 详情失败: {}", externalUserId, e.getMessage());
|
||||
}
|
||||
}
|
||||
} catch (WxErrorException e) {
|
||||
log.warn("[WeComSync] 获取成员 {} 的客户列表失败: {}", userId, e.getMessage());
|
||||
}
|
||||
return contacts;
|
||||
}
|
||||
|
||||
private WeComConversation fetchGroupChatDetail(WxCpService service, String chatId) {
|
||||
try {
|
||||
WxCpUserExternalGroupChatInfo detail = service.getExternalContactService().getGroupChat(chatId, 0);
|
||||
if (detail == null || detail.getGroupChat() == null) {
|
||||
return null;
|
||||
}
|
||||
WxCpUserExternalGroupChatInfo.GroupChat groupChat = detail.getGroupChat();
|
||||
List<WeComConversationMember> members = new ArrayList<>();
|
||||
if (groupChat.getMemberList() != null) {
|
||||
for (WxCpUserExternalGroupChatInfo.GroupMember member : groupChat.getMemberList()) {
|
||||
members.add(WeComConversationMember.builder()
|
||||
.userId(member.getUserId())
|
||||
.name(resolveMemberName(service, member))
|
||||
.build());
|
||||
}
|
||||
}
|
||||
return WeComConversation.builder()
|
||||
.chatId(groupChat.getChatId())
|
||||
.chatName(groupChat.getName())
|
||||
.ownerUserId(groupChat.getOwner())
|
||||
.memberList(members)
|
||||
.build();
|
||||
} catch (WxErrorException e) {
|
||||
log.warn("[WeComSync] 获取群聊 {} 详情失败: {}", chatId, e.getMessage());
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
log.warn("[WeComSync] 获取群聊 {} 详情异常", chatId, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String resolveMemberName(WxCpService service, WxCpUserExternalGroupChatInfo.GroupMember member) {
|
||||
if (StringUtils.isNotBlank(member.getName())) {
|
||||
return member.getName();
|
||||
}
|
||||
String userId = member.getUserId();
|
||||
if (StringUtils.isBlank(userId)) {
|
||||
return null;
|
||||
}
|
||||
// 外部联系人:通过客户详情接口补全名称
|
||||
if (userId.startsWith("wm")) {
|
||||
try {
|
||||
WxCpExternalContactInfo info = service.getExternalContactService().getContactDetail(userId, null);
|
||||
if (info != null && info.getExternalContact() != null
|
||||
&& StringUtils.isNotBlank(info.getExternalContact().getName())) {
|
||||
return info.getExternalContact().getName();
|
||||
}
|
||||
} catch (WxErrorException e) {
|
||||
log.debug("[WeComSync] 获取外部联系人 {} 名称失败: {}", userId, e.getMessage());
|
||||
}
|
||||
}
|
||||
// 内部成员:通过通讯录接口补全名称
|
||||
try {
|
||||
WxCpUser user = service.getUserService().getById(userId);
|
||||
if (user != null && StringUtils.isNotBlank(user.getName())) {
|
||||
return user.getName();
|
||||
}
|
||||
} catch (WxErrorException e) {
|
||||
log.debug("[WeComSync] 获取内部成员 {} 名称失败: {}", userId, e.getMessage());
|
||||
}
|
||||
return userId;
|
||||
}
|
||||
}
|
||||
@@ -12,18 +12,28 @@ import java.util.List;
|
||||
public interface WeComSyncClient {
|
||||
|
||||
/**
|
||||
* 同步联系人(企业内部成员 + 外部联系人)。
|
||||
* 同步企业通讯录(部门 + 内部成员)。
|
||||
*
|
||||
* @param config 企微配置
|
||||
* @return 联系人列表
|
||||
*/
|
||||
List<WeComContact> syncContacts(WeComSyncConfig config);
|
||||
List<WeComContact> syncEnterpriseContacts(WeComSyncConfig config);
|
||||
|
||||
/**
|
||||
* 同步群聊(客户群)。
|
||||
* 同步某个成员的客户(外部联系人)。
|
||||
*
|
||||
* @param config 企微配置
|
||||
* @param userId 成员 userId
|
||||
* @return 联系人列表
|
||||
*/
|
||||
List<WeComContact> syncAccountContacts(WeComSyncConfig config, String userId);
|
||||
|
||||
/**
|
||||
* 同步某个成员的客户群。
|
||||
*
|
||||
* @param config 企微配置
|
||||
* @param userId 成员 userId
|
||||
* @return 群聊列表
|
||||
*/
|
||||
List<WeComConversation> syncConversations(WeComSyncConfig config);
|
||||
List<WeComConversation> syncAccountConversations(WeComSyncConfig config, String userId);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user