- 新增 XuqmGroup 部署文档,包含部署方案、架构建议和部署步骤 - 添加安全设计规范,涵盖密码安全、AppSecret验证和服务端API认证 - 补充平台REST API规范,定义Server-to-Server调用接口和错误码 - 创建Java IM服务端SDK计划文档,规划Maven包发布和接口实现
320 行
14 KiB
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
|
||
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.findByAppKeyAndFromUserIdAndToUserId(appKey, fromUserId, toUserId)
|
||
.orElseGet(() -> {
|
||
created[0] = true;
|
||
ImFriendRequestEntity entity = new ImFriendRequestEntity();
|
||
entity.setId(UUID.randomUUID().toString());
|
||
entity.setAppKey(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
|
||
.findByAppKeyAndUserIdAndFriendId(appKey, request.getFromUserId(), request.getToUserId())
|
||
.orElseGet(() -> friendEntity(appKey, request.getFromUserId(), request.getToUserId()));
|
||
friendRepository
|
||
.findByAppKeyAndUserIdAndFriendId(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<ImFriendRequestEntity> acceptBatch(String appKey, List<String> requestIds, String operatorId) {
|
||
List<ImFriendRequestEntity> result = new java.util.ArrayList<>();
|
||
for (String requestId : unique(requestIds)) {
|
||
result.add(acceptInternal(appKey, requestId, operatorId));
|
||
}
|
||
return result;
|
||
}
|
||
|
||
@Transactional
|
||
public List<ImFriendRequestEntity> rejectBatch(String appKey, List<String> requestIds, String operatorId) {
|
||
List<ImFriendRequestEntity> result = new java.util.ArrayList<>();
|
||
for (String requestId : unique(requestIds)) {
|
||
result.add(rejectInternal(appKey, requestId, operatorId));
|
||
}
|
||
return result;
|
||
}
|
||
|
||
public List<ImFriendRequestEntity> incoming(String appKey, String userId) {
|
||
return requestRepository.findByAppKeyAndToUserId(appKey, userId).stream()
|
||
.filter(request -> ImFriendRequestEntity.Status.PENDING.name().equals(request.getStatus()))
|
||
.toList();
|
||
}
|
||
|
||
public List<ImFriendRequestEntity> outgoing(String appKey, String userId) {
|
||
return requestRepository.findByAppKeyAndFromUserId(appKey, userId);
|
||
}
|
||
|
||
public List<ImFriendRequestEntity> listByApp(String appKey) {
|
||
return requestRepository.findByAppKey(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.getAppKey().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.getAppKey().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
|
||
.findByAppKeyAndUserIdAndFriendId(request.getAppKey(), request.getFromUserId(), request.getToUserId())
|
||
.orElseGet(() -> friendEntity(request.getAppKey(), request.getFromUserId(), request.getToUserId()));
|
||
friendRepository
|
||
.findByAppKeyAndUserIdAndFriendId(request.getAppKey(), request.getToUserId(), request.getFromUserId())
|
||
.orElseGet(() -> friendEntity(request.getAppKey(), 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.setAppKey(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.setAppKey(request.getAppKey());
|
||
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.getAppKey(),
|
||
"friend_request",
|
||
callbackEvent,
|
||
new FriendRequestCallbackPayload(
|
||
request.getAppKey(),
|
||
request.getId(),
|
||
request.getFromUserId(),
|
||
request.getToUserId(),
|
||
request.getRemark(),
|
||
request.getStatus(),
|
||
request.getReviewedAt() == null ? null : request.getReviewedAt().atZone(java.time.ZoneId.systemDefault()).toInstant().toEpochMilli()
|
||
)
|
||
);
|
||
}
|
||
}
|