package com.xuqm.im.service; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.xuqm.common.exception.BusinessException; import com.xuqm.im.cluster.ImClusterPublisher; import com.xuqm.im.entity.ImFriendRequestEntity; import com.xuqm.im.entity.ImMessageEntity; import com.xuqm.im.model.FriendRequestCallbackPayload; import com.xuqm.im.repository.ImFriendRequestRepository; import com.xuqm.im.repository.ImFriendRepository; import com.xuqm.im.repository.ImMessageRepository; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.List; import java.util.UUID; @Service public class FriendRequestService { private final ImFriendRequestRepository requestRepository; private final ImFriendRepository friendRepository; private final ImMessageRepository messageRepository; private final ImClusterPublisher clusterPublisher; private final ImFeatureConfigClient featureConfigClient; private final WebhookDispatchService webhookDispatchService; private final ObjectMapper objectMapper; public FriendRequestService(ImFriendRequestRepository requestRepository, ImFriendRepository friendRepository, ImMessageRepository messageRepository, ImClusterPublisher clusterPublisher, ImFeatureConfigClient featureConfigClient, WebhookDispatchService webhookDispatchService, ObjectMapper objectMapper) { this.requestRepository = requestRepository; this.friendRepository = friendRepository; this.messageRepository = messageRepository; this.clusterPublisher = clusterPublisher; this.featureConfigClient = featureConfigClient; this.webhookDispatchService = webhookDispatchService; this.objectMapper = objectMapper; } @Transactional public ImFriendRequestEntity send(String appKey, String fromUserId, String toUserId, String remark) { String mode = featureConfigClient.friendRequestMode(appKey); if ("DISALLOW".equals(mode)) { throw new BusinessException(403, "当前应用未开放好友申请"); } final boolean[] created = {false}; ImFriendRequestEntity saved = requestRepository.findByAppIdAndFromUserIdAndToUserId(appKey, fromUserId, toUserId) .orElseGet(() -> { created[0] = true; ImFriendRequestEntity entity = new ImFriendRequestEntity(); entity.setId(UUID.randomUUID().toString()); entity.setAppId(appKey); entity.setFromUserId(fromUserId); entity.setToUserId(toUserId); entity.setRemark(remark); entity.setStatus(ImFriendRequestEntity.Status.PENDING.name()); entity.setCreatedAt(LocalDateTime.now()); return requestRepository.save(entity); }); if ("DIRECT_ACCEPT".equals(mode)) { if (ImFriendRequestEntity.Status.ACCEPTED.name().equals(saved.getStatus())) { return saved; } return acceptRequest(saved); } if (!ImFriendRequestEntity.Status.PENDING.name().equals(saved.getStatus())) { return saved; } if (created[0]) { dispatchWebhook(saved, "friend.request.sent"); } publishNotification( saved, saved.getFromUserId(), saved.getToUserId(), "FRIEND_REQUEST", "好友申请", buildDescription("好友申请", saved.getRemark()) ); return saved; } @Transactional public ImFriendRequestEntity accept(String appKey, String requestId, String operatorId) { ImFriendRequestEntity request = getRequest(appKey, requestId, operatorId); request.setStatus(ImFriendRequestEntity.Status.ACCEPTED.name()); request.setReviewedAt(LocalDateTime.now()); requestRepository.save(request); friendRepository .findByAppIdAndUserIdAndFriendId(appKey, request.getFromUserId(), request.getToUserId()) .orElseGet(() -> friendEntity(appKey, request.getFromUserId(), request.getToUserId())); friendRepository .findByAppIdAndUserIdAndFriendId(appKey, request.getToUserId(), request.getFromUserId()) .orElseGet(() -> friendEntity(appKey, request.getToUserId(), request.getFromUserId())); dispatchWebhook(request, "friend.request.accepted"); publishNotification( request, request.getToUserId(), request.getFromUserId(), "FRIEND_REQUEST_STATUS", "好友申请已通过", buildDescription("好友申请已通过", request.getRemark()) ); return request; } @Transactional public ImFriendRequestEntity reject(String appKey, String requestId, String operatorId) { ImFriendRequestEntity request = getRequest(appKey, requestId, operatorId); request.setStatus(ImFriendRequestEntity.Status.REJECTED.name()); request.setReviewedAt(LocalDateTime.now()); ImFriendRequestEntity saved = requestRepository.save(request); dispatchWebhook(saved, "friend.request.rejected"); publishNotification( saved, saved.getToUserId(), saved.getFromUserId(), "FRIEND_REQUEST_STATUS", "好友申请已拒绝", buildDescription("好友申请已拒绝", saved.getRemark()) ); return saved; } @Transactional public List acceptBatch(String appKey, List requestIds, String operatorId) { List result = new java.util.ArrayList<>(); for (String requestId : unique(requestIds)) { result.add(acceptInternal(appKey, requestId, operatorId)); } return result; } @Transactional public List rejectBatch(String appKey, List requestIds, String operatorId) { List result = new java.util.ArrayList<>(); for (String requestId : unique(requestIds)) { result.add(rejectInternal(appKey, requestId, operatorId)); } return result; } public List incoming(String appKey, String userId) { return requestRepository.findByAppIdAndToUserId(appKey, userId).stream() .filter(request -> ImFriendRequestEntity.Status.PENDING.name().equals(request.getStatus())) .toList(); } public List outgoing(String appKey, String userId) { return requestRepository.findByAppIdAndFromUserId(appKey, userId); } public List listByApp(String appKey) { return requestRepository.findByAppId(appKey); } @Transactional public ImFriendRequestEntity adminAccept(String appKey, String requestId) { ImFriendRequestEntity request = getRequest(appKey, requestId); return acceptRequest(request); } @Transactional public ImFriendRequestEntity adminReject(String appKey, String requestId) { ImFriendRequestEntity request = getRequest(appKey, requestId); return rejectRequest(request); } private ImFriendRequestEntity getRequest(String appKey, String requestId, String operatorId) { ImFriendRequestEntity request = requestRepository.findById(requestId) .orElseThrow(() -> new BusinessException(404, "好友申请不存在")); if (!request.getAppId().equals(appKey) || !request.getToUserId().equals(operatorId)) { throw new BusinessException(403, "无权操作"); } return request; } private ImFriendRequestEntity getRequest(String appKey, String requestId) { ImFriendRequestEntity request = requestRepository.findById(requestId) .orElseThrow(() -> new BusinessException(404, "好友申请不存在")); if (!request.getAppId().equals(appKey)) { throw new BusinessException(403, "无权操作"); } return request; } private ImFriendRequestEntity acceptInternal(String appKey, String requestId, String operatorId) { ImFriendRequestEntity request = getRequest(appKey, requestId, operatorId); return acceptRequest(request); } private ImFriendRequestEntity acceptRequest(ImFriendRequestEntity request) { request.setStatus(ImFriendRequestEntity.Status.ACCEPTED.name()); request.setReviewedAt(LocalDateTime.now()); ImFriendRequestEntity saved = requestRepository.save(request); friendRepository .findByAppIdAndUserIdAndFriendId(request.getAppId(), request.getFromUserId(), request.getToUserId()) .orElseGet(() -> friendEntity(request.getAppId(), request.getFromUserId(), request.getToUserId())); friendRepository .findByAppIdAndUserIdAndFriendId(request.getAppId(), request.getToUserId(), request.getFromUserId()) .orElseGet(() -> friendEntity(request.getAppId(), request.getToUserId(), request.getFromUserId())); dispatchWebhook(saved, "friend.request.accepted"); publishNotification( request, request.getToUserId(), request.getFromUserId(), "FRIEND_REQUEST_STATUS", "好友申请已通过", buildDescription("好友申请已通过", request.getRemark()) ); return saved; } private ImFriendRequestEntity rejectInternal(String appKey, String requestId, String operatorId) { ImFriendRequestEntity request = getRequest(appKey, requestId, operatorId); return rejectRequest(request); } private com.xuqm.im.entity.ImFriendEntity friendEntity(String appKey, String userId, String friendId) { com.xuqm.im.entity.ImFriendEntity entity = new com.xuqm.im.entity.ImFriendEntity(); entity.setAppId(appKey); entity.setUserId(userId); entity.setFriendId(friendId); return friendRepository.save(entity); } private List unique(List requestIds) { return requestIds == null ? List.of() : new java.util.ArrayList<>(new java.util.LinkedHashSet<>(requestIds)); } private void publishNotification( ImFriendRequestEntity request, String fromUserId, String toUserId, String type, String title, String content ) { ImMessageEntity message = new ImMessageEntity(); message.setId(UUID.randomUUID().toString()); message.setAppId(request.getAppId()); message.setFromUserId(fromUserId); message.setToId(toUserId); message.setChatType(ImMessageEntity.ChatType.SINGLE); message.setMsgType(ImMessageEntity.MsgType.NOTIFY); message.setContent(buildNotificationContent(type, title, content, request)); message.setStatus(ImMessageEntity.MsgStatus.SENT); message.setCreatedAt(LocalDateTime.now()); ImMessageEntity saved = messageRepository.save(message); clusterPublisher.publish("/user/" + toUserId + "/queue/messages", saved); } private String buildNotificationContent( String type, String title, String content, ImFriendRequestEntity request ) { ObjectNode node = objectMapper.createObjectNode(); node.put("type", type); node.put("title", title); node.put("content", content); node.put("requestId", request.getId()); node.put("fromUserId", request.getFromUserId()); node.put("toUserId", request.getToUserId()); node.put("status", request.getStatus()); if (request.getRemark() != null) { node.put("remark", request.getRemark()); } return node.toString(); } private String buildDescription(String prefix, String remark) { if (remark == null || remark.isBlank()) { return prefix; } return prefix + ":" + remark; } private ImFriendRequestEntity rejectRequest(ImFriendRequestEntity request) { request.setStatus(ImFriendRequestEntity.Status.REJECTED.name()); request.setReviewedAt(LocalDateTime.now()); ImFriendRequestEntity saved = requestRepository.save(request); dispatchWebhook(saved, "friend.request.rejected"); publishNotification( saved, saved.getToUserId(), saved.getFromUserId(), "FRIEND_REQUEST_STATUS", "好友申请已拒绝", buildDescription("好友申请已拒绝", saved.getRemark()) ); return saved; } private void dispatchWebhook(ImFriendRequestEntity request, String callbackEvent) { webhookDispatchService.dispatch( request.getAppId(), "friend_request", callbackEvent, new FriendRequestCallbackPayload( request.getAppId(), request.getId(), request.getFromUserId(), request.getToUserId(), request.getRemark(), request.getStatus(), request.getReviewedAt() == null ? null : request.getReviewedAt().atZone(java.time.ZoneId.systemDefault()).toInstant().toEpochMilli() ) ); } }