XuqmGroup-Server/im-service/src/main/java/com/xuqm/im/service/FriendRequestService.java

320 行
14 KiB
Java

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
2026-05-07 19:39:42 +08:00
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};
2026-05-07 19:39:42 +08:00
ImFriendRequestEntity saved = requestRepository.findByAppIdAndFromUserIdAndToUserId(appKey, fromUserId, toUserId)
.orElseGet(() -> {
created[0] = true;
ImFriendRequestEntity entity = new ImFriendRequestEntity();
entity.setId(UUID.randomUUID().toString());
2026-05-07 19:39:42 +08:00
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
2026-05-07 19:39:42 +08:00
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
2026-05-07 19:39:42 +08:00
.findByAppIdAndUserIdAndFriendId(appKey, request.getFromUserId(), request.getToUserId())
.orElseGet(() -> friendEntity(appKey, request.getFromUserId(), request.getToUserId()));
friendRepository
2026-05-07 19:39:42 +08:00
.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
2026-05-07 19:39:42 +08:00
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
2026-05-07 19:39:42 +08:00
public List<ImFriendRequestEntity> acceptBatch(String appKey, List<String> requestIds, String operatorId) {
List<ImFriendRequestEntity> result = new java.util.ArrayList<>();
for (String requestId : unique(requestIds)) {
2026-05-07 19:39:42 +08:00
result.add(acceptInternal(appKey, requestId, operatorId));
}
return result;
}
@Transactional
2026-05-07 19:39:42 +08:00
public List<ImFriendRequestEntity> rejectBatch(String appKey, List<String> requestIds, String operatorId) {
List<ImFriendRequestEntity> result = new java.util.ArrayList<>();
for (String requestId : unique(requestIds)) {
2026-05-07 19:39:42 +08:00
result.add(rejectInternal(appKey, requestId, operatorId));
}
return result;
}
2026-05-07 19:39:42 +08:00
public List<ImFriendRequestEntity> incoming(String appKey, String userId) {
return requestRepository.findByAppIdAndToUserId(appKey, userId).stream()
.filter(request -> ImFriendRequestEntity.Status.PENDING.name().equals(request.getStatus()))
.toList();
}
2026-05-07 19:39:42 +08:00
public List<ImFriendRequestEntity> outgoing(String appKey, String userId) {
return requestRepository.findByAppIdAndFromUserId(appKey, userId);
}
2026-05-07 19:39:42 +08:00
public List<ImFriendRequestEntity> listByApp(String appKey) {
return requestRepository.findByAppId(appKey);
}
@Transactional
2026-05-07 19:39:42 +08:00
public ImFriendRequestEntity adminAccept(String appKey, String requestId) {
ImFriendRequestEntity request = getRequest(appKey, requestId);
return acceptRequest(request);
}
@Transactional
2026-05-07 19:39:42 +08:00
public ImFriendRequestEntity adminReject(String appKey, String requestId) {
ImFriendRequestEntity request = getRequest(appKey, requestId);
return rejectRequest(request);
}
2026-05-07 19:39:42 +08:00
private ImFriendRequestEntity getRequest(String appKey, String requestId, String operatorId) {
ImFriendRequestEntity request = requestRepository.findById(requestId)
.orElseThrow(() -> new BusinessException(404, "好友申请不存在"));
2026-05-07 19:39:42 +08:00
if (!request.getAppId().equals(appKey) || !request.getToUserId().equals(operatorId)) {
throw new BusinessException(403, "无权操作");
}
return request;
}
2026-05-07 19:39:42 +08:00
private ImFriendRequestEntity getRequest(String appKey, String requestId) {
ImFriendRequestEntity request = requestRepository.findById(requestId)
.orElseThrow(() -> new BusinessException(404, "好友申请不存在"));
2026-05-07 19:39:42 +08:00
if (!request.getAppId().equals(appKey)) {
throw new BusinessException(403, "无权操作");
}
return request;
}
2026-05-07 19:39:42 +08:00
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;
}
2026-05-07 19:39:42 +08:00
private ImFriendRequestEntity rejectInternal(String appKey, String requestId, String operatorId) {
ImFriendRequestEntity request = getRequest(appKey, requestId, operatorId);
return rejectRequest(request);
}
2026-05-07 19:39:42 +08:00
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();
2026-05-07 19:39:42 +08:00
entity.setAppId(appKey);
entity.setUserId(userId);
entity.setFriendId(friendId);
return friendRepository.save(entity);
}
private List<String> unique(List<String> 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()
)
);
}
}