feat: 新增渠道身份菜单、会话成员/标签展示、修复群聊成员 person_type 及重复同步问题
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
package com.sino.mci.channel.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.sino.mci.admin.security.AuthContext;
|
||||
import com.sino.mci.channel.dto.ChannelIdentityResponse;
|
||||
import com.sino.mci.channel.entity.ChannelSubject;
|
||||
import com.sino.mci.channel.repository.ChannelSubjectMapper;
|
||||
import com.sino.mci.crm.entity.ChannelIdentity;
|
||||
import com.sino.mci.crm.entity.Person;
|
||||
import com.sino.mci.crm.repository.ChannelIdentityMapper;
|
||||
import com.sino.mci.crm.repository.PersonMapper;
|
||||
import com.sino.mci.shared.web.ApiResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/channel-identity")
|
||||
@RequiredArgsConstructor
|
||||
public class ChannelIdentityController {
|
||||
|
||||
private final ChannelIdentityMapper channelIdentityMapper;
|
||||
private final PersonMapper personMapper;
|
||||
private final ChannelSubjectMapper channelSubjectMapper;
|
||||
|
||||
@GetMapping("/list")
|
||||
public ApiResponse<List<ChannelIdentityResponse>> listChannelIdentities() {
|
||||
String tenantCode = AuthContext.currentTenantCode();
|
||||
List<ChannelIdentity> identities = channelIdentityMapper.selectList(
|
||||
new LambdaQueryWrapper<ChannelIdentity>()
|
||||
.eq(ChannelIdentity::getTenantCode, tenantCode)
|
||||
.orderByDesc(ChannelIdentity::getId));
|
||||
|
||||
if (identities.isEmpty()) {
|
||||
return ApiResponse.ok(Collections.emptyList());
|
||||
}
|
||||
|
||||
Set<Long> personIds = identities.stream()
|
||||
.map(ChannelIdentity::getPersonId)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
Set<Long> subjectIds = identities.stream()
|
||||
.map(ChannelIdentity::getSubjectId)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
Map<Long, Person> personMap = personIds.isEmpty() ? Collections.emptyMap()
|
||||
: personMapper.selectBatchIds(personIds).stream()
|
||||
.collect(Collectors.toMap(Person::getId, p -> p));
|
||||
Map<Long, ChannelSubject> subjectMap = subjectIds.isEmpty() ? Collections.emptyMap()
|
||||
: channelSubjectMapper.selectBatchIds(subjectIds).stream()
|
||||
.collect(Collectors.toMap(ChannelSubject::getId, s -> s));
|
||||
|
||||
List<ChannelIdentityResponse> responses = identities.stream().map(identity -> {
|
||||
ChannelIdentityResponse response = new ChannelIdentityResponse();
|
||||
response.setId(identity.getId());
|
||||
response.setTenantCode(identity.getTenantCode());
|
||||
response.setChannelType(identity.getChannelType());
|
||||
response.setChannelUserId(identity.getChannelUserId());
|
||||
response.setChannelUserName(identity.getChannelUserName());
|
||||
response.setSubjectId(identity.getSubjectId());
|
||||
ChannelSubject subject = identity.getSubjectId() == null ? null : subjectMap.get(identity.getSubjectId());
|
||||
response.setSubjectName(subject == null ? null : subject.getSubjectName());
|
||||
response.setPersonId(identity.getPersonId());
|
||||
Person person = identity.getPersonId() == null ? null : personMap.get(identity.getPersonId());
|
||||
response.setPersonName(person == null ? null : person.getPersonName());
|
||||
response.setPersonType(person == null ? null : person.getPersonType());
|
||||
response.setStatus(identity.getStatus());
|
||||
return response;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
return ApiResponse.ok(responses);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,9 @@ package com.sino.mci.channel.controller;
|
||||
import com.sino.mci.admin.security.AuthContext;
|
||||
import com.sino.mci.channel.dto.BindConversationAccountRequest;
|
||||
import com.sino.mci.channel.dto.BindConversationTagsRequest;
|
||||
import com.sino.mci.channel.dto.ConversationParticipantResponse;
|
||||
import com.sino.mci.channel.dto.CreateConversationRequest;
|
||||
import com.sino.mci.channel.dto.TagResponse;
|
||||
import com.sino.mci.channel.dto.UpdateConversationRequest;
|
||||
import com.sino.mci.channel.entity.Conversation;
|
||||
import com.sino.mci.channel.service.ConversationService;
|
||||
@@ -73,4 +75,18 @@ public class ConversationController {
|
||||
conversationService.bindTags(tenantCode, conversationId, request);
|
||||
return ApiResponse.ok();
|
||||
}
|
||||
|
||||
@GetMapping("/{conversation_id}/participants")
|
||||
public ApiResponse<List<ConversationParticipantResponse>> listParticipants(
|
||||
@RequestHeader("X-Tenant-Code") String tenantCode,
|
||||
@PathVariable("conversation_id") Long conversationId) {
|
||||
return ApiResponse.ok(conversationService.listParticipants(tenantCode, conversationId));
|
||||
}
|
||||
|
||||
@GetMapping("/{conversation_id}/tags")
|
||||
public ApiResponse<List<TagResponse>> listTags(
|
||||
@RequestHeader("X-Tenant-Code") String tenantCode,
|
||||
@PathVariable("conversation_id") Long conversationId) {
|
||||
return ApiResponse.ok(conversationService.listTags(tenantCode, conversationId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.sino.mci.channel.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ChannelIdentityResponse {
|
||||
|
||||
private Long id;
|
||||
private String tenantCode;
|
||||
private String channelType;
|
||||
private String channelUserId;
|
||||
private String channelUserName;
|
||||
private Long subjectId;
|
||||
private String subjectName;
|
||||
private Long personId;
|
||||
private String personName;
|
||||
private Integer personType;
|
||||
private Integer status;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.sino.mci.channel.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class ConversationParticipantResponse {
|
||||
|
||||
private Long id;
|
||||
private Integer participantType;
|
||||
private LocalDateTime joinTime;
|
||||
private Long channelIdentityId;
|
||||
private String channelUserId;
|
||||
private String channelUserName;
|
||||
private Long personId;
|
||||
private String personName;
|
||||
private Integer personType;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.sino.mci.channel.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class TagResponse {
|
||||
|
||||
private Long id;
|
||||
private String tenantCode;
|
||||
private Long parentId;
|
||||
private String tagPath;
|
||||
private Integer level;
|
||||
private String tagName;
|
||||
private String tagSource;
|
||||
private Integer status;
|
||||
}
|
||||
@@ -2,8 +2,12 @@ package com.sino.mci.channel.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.sino.mci.channel.entity.ConversationParticipant;
|
||||
import org.apache.ibatis.annotations.Delete;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface ConversationParticipantMapper extends BaseMapper<ConversationParticipant> {
|
||||
|
||||
@Delete("DELETE FROM mci_conversation_participant WHERE conversation_id = #{conversationId}")
|
||||
void physicalDeleteByConversationId(Long conversationId);
|
||||
}
|
||||
|
||||
@@ -3,17 +3,25 @@ package com.sino.mci.channel.service;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.sino.mci.channel.dto.BindConversationAccountRequest;
|
||||
import com.sino.mci.channel.dto.BindConversationTagsRequest;
|
||||
import com.sino.mci.channel.dto.ConversationParticipantResponse;
|
||||
import com.sino.mci.channel.dto.CreateConversationRequest;
|
||||
import com.sino.mci.channel.dto.TagResponse;
|
||||
import com.sino.mci.channel.dto.UpdateConversationRequest;
|
||||
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.entity.ConversationParticipant;
|
||||
import com.sino.mci.channel.entity.ConversationTagBinding;
|
||||
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.repository.ConversationParticipantMapper;
|
||||
import com.sino.mci.channel.repository.ConversationTagBindingMapper;
|
||||
import com.sino.mci.crm.entity.ChannelIdentity;
|
||||
import com.sino.mci.crm.entity.Person;
|
||||
import com.sino.mci.crm.entity.Tag;
|
||||
import com.sino.mci.crm.repository.ChannelIdentityMapper;
|
||||
import com.sino.mci.crm.repository.PersonMapper;
|
||||
import com.sino.mci.crm.repository.TagMapper;
|
||||
import com.sino.mci.exception.BusinessException;
|
||||
import com.sino.mci.shared.util.JsonUtils;
|
||||
@@ -22,7 +30,12 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@@ -34,6 +47,9 @@ public class ConversationService {
|
||||
private final ConversationTagBindingMapper conversationTagBindingMapper;
|
||||
private final TagMapper tagMapper;
|
||||
private final ChannelAccountMapper accountMapper;
|
||||
private final ConversationParticipantMapper participantMapper;
|
||||
private final ChannelIdentityMapper channelIdentityMapper;
|
||||
private final PersonMapper personMapper;
|
||||
|
||||
public List<Conversation> listConversations(String tenantCode) {
|
||||
return conversationMapper.selectList(
|
||||
@@ -160,4 +176,95 @@ public class ConversationService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<ConversationParticipantResponse> listParticipants(String tenantCode, Long conversationId) {
|
||||
getConversation(tenantCode, conversationId);
|
||||
|
||||
List<ConversationParticipant> participants = participantMapper.selectList(
|
||||
new LambdaQueryWrapper<ConversationParticipant>()
|
||||
.eq(ConversationParticipant::getConversationId, conversationId)
|
||||
.orderByAsc(ConversationParticipant::getId));
|
||||
|
||||
if (participants.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
Set<Long> channelIdentityIds = participants.stream()
|
||||
.map(ConversationParticipant::getChannelIdentityId)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
Set<Long> personIds = participants.stream()
|
||||
.map(ConversationParticipant::getPersonId)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
Map<Long, ChannelIdentity> identityMap = channelIdentityIds.isEmpty() ? Collections.emptyMap()
|
||||
: channelIdentityMapper.selectBatchIds(channelIdentityIds).stream()
|
||||
.collect(Collectors.toMap(ChannelIdentity::getId, Function.identity()));
|
||||
Map<Long, Person> personMap = personIds.isEmpty() ? Collections.emptyMap()
|
||||
: personMapper.selectBatchIds(personIds).stream()
|
||||
.collect(Collectors.toMap(Person::getId, Function.identity()));
|
||||
|
||||
return participants.stream().map(participant -> {
|
||||
ConversationParticipantResponse response = new ConversationParticipantResponse();
|
||||
response.setId(participant.getId());
|
||||
response.setParticipantType(participant.getParticipantType());
|
||||
response.setJoinTime(participant.getJoinTime());
|
||||
response.setChannelIdentityId(participant.getChannelIdentityId());
|
||||
|
||||
ChannelIdentity identity = participant.getChannelIdentityId() == null
|
||||
? null : identityMap.get(participant.getChannelIdentityId());
|
||||
response.setChannelUserId(identity == null ? null : identity.getChannelUserId());
|
||||
response.setChannelUserName(identity == null ? null : identity.getChannelUserName());
|
||||
|
||||
Person person = participant.getPersonId() == null
|
||||
? null : personMap.get(participant.getPersonId());
|
||||
response.setPersonId(participant.getPersonId());
|
||||
response.setPersonName(person == null ? null : person.getPersonName());
|
||||
response.setPersonType(person == null ? null : person.getPersonType());
|
||||
|
||||
return response;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<TagResponse> listTags(String tenantCode, Long conversationId) {
|
||||
getConversation(tenantCode, conversationId);
|
||||
|
||||
List<ConversationTagBinding> bindings = conversationTagBindingMapper.selectList(
|
||||
new LambdaQueryWrapper<ConversationTagBinding>()
|
||||
.eq(ConversationTagBinding::getConversationId, conversationId)
|
||||
.orderByAsc(ConversationTagBinding::getId));
|
||||
|
||||
if (bindings.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
Set<Long> tagIds = bindings.stream()
|
||||
.map(ConversationTagBinding::getTagId)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
Map<Long, Tag> tagMap = tagMapper.selectBatchIds(tagIds).stream()
|
||||
.collect(Collectors.toMap(Tag::getId, Function.identity()));
|
||||
|
||||
return bindings.stream()
|
||||
.map(binding -> {
|
||||
Tag tag = tagMap.get(binding.getTagId());
|
||||
if (tag == null) {
|
||||
return null;
|
||||
}
|
||||
TagResponse response = new TagResponse();
|
||||
response.setId(tag.getId());
|
||||
response.setTenantCode(tag.getTenantCode());
|
||||
response.setParentId(tag.getParentId());
|
||||
response.setTagPath(tag.getTagPath());
|
||||
response.setLevel(tag.getLevel());
|
||||
response.setTagName(tag.getTagName());
|
||||
response.setTagSource(tag.getTagSource());
|
||||
response.setStatus(tag.getStatus());
|
||||
return response;
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,11 +135,10 @@ public class SyncDataPersister {
|
||||
|
||||
@Transactional
|
||||
public void refreshParticipants(String tenantCode, Long subjectId, Conversation conversation, WeComConversation conv) {
|
||||
conversationParticipantMapper.delete(
|
||||
new LambdaQueryWrapper<ConversationParticipant>()
|
||||
.eq(ConversationParticipant::getConversationId, conversation.getId()));
|
||||
conversationParticipantMapper.physicalDeleteByConversationId(conversation.getId());
|
||||
|
||||
for (WeComConversationMember member : conv.getMemberList()) {
|
||||
log.debug("[SyncDataPersister] 群聊成员 userId={}, type={}", member.getUserId(), member.getType());
|
||||
ChannelIdentity identity = findOrCreateIdentity(tenantCode, subjectId, member);
|
||||
|
||||
ConversationParticipant participant = new ConversationParticipant();
|
||||
@@ -165,7 +164,13 @@ public class SyncDataPersister {
|
||||
.eq(ChannelIdentity::getChannelType, CHANNEL_WECOM)
|
||||
.eq(ChannelIdentity::getChannelUserId, member.getUserId()));
|
||||
|
||||
int personType = resolveMemberPersonType(member.getType());
|
||||
if (identity != null) {
|
||||
Person existPerson = personMapper.selectById(identity.getPersonId());
|
||||
if (existPerson != null && !existPerson.getPersonType().equals(personType)) {
|
||||
existPerson.setPersonType(personType);
|
||||
personMapper.updateById(existPerson);
|
||||
}
|
||||
return identity;
|
||||
}
|
||||
|
||||
@@ -174,7 +179,7 @@ public class SyncDataPersister {
|
||||
person.setTenantCode(tenantCode);
|
||||
person.setPersonCode(member.getUserId());
|
||||
person.setPersonName(displayName);
|
||||
person.setPersonType(PERSON_TYPE_CUSTOMER);
|
||||
person.setPersonType(personType);
|
||||
person.setStatus(1);
|
||||
personMapper.insert(person);
|
||||
|
||||
@@ -197,6 +202,11 @@ public class SyncDataPersister {
|
||||
return userId != null && !userId.isBlank() ? userId : "未知";
|
||||
}
|
||||
|
||||
private int resolveMemberPersonType(Integer memberType) {
|
||||
// 企微群聊成员类型:1=企业成员,2=外部联系人
|
||||
return memberType != null && memberType == 1 ? PERSON_TYPE_INTERNAL : PERSON_TYPE_CUSTOMER;
|
||||
}
|
||||
|
||||
private void ensureBinding(Long accountId, Long conversationId, int bindingType) {
|
||||
ChannelAccountConversation existing = accountConversationMapper.selectOne(
|
||||
new LambdaQueryWrapper<ChannelAccountConversation>()
|
||||
|
||||
Reference in New Issue
Block a user