2026-04-21 22:07:29 +08:00
|
|
|
|
package com.xuqm.im.service;
|
|
|
|
|
|
|
|
|
|
|
|
import com.fasterxml.jackson.core.type.TypeReference;
|
|
|
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
2026-04-28 20:11:38 +08:00
|
|
|
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
2026-04-21 22:07:29 +08:00
|
|
|
|
import com.xuqm.common.exception.BusinessException;
|
2026-04-28 20:11:38 +08:00
|
|
|
|
import com.xuqm.im.cluster.ImClusterPublisher;
|
2026-04-21 22:07:29 +08:00
|
|
|
|
import com.xuqm.im.entity.ImGroupEntity;
|
2026-04-28 09:45:20 +08:00
|
|
|
|
import com.xuqm.im.entity.ImGroupJoinRequestEntity;
|
2026-04-28 20:11:38 +08:00
|
|
|
|
import com.xuqm.im.entity.ImMessageEntity;
|
2026-04-27 23:41:58 +08:00
|
|
|
|
import com.xuqm.im.entity.ImGroupMuteEntity;
|
2026-04-28 09:45:20 +08:00
|
|
|
|
import com.xuqm.im.repository.ImGroupJoinRequestRepository;
|
2026-04-21 22:07:29 +08:00
|
|
|
|
import com.xuqm.im.repository.ImGroupRepository;
|
2026-04-27 23:41:58 +08:00
|
|
|
|
import com.xuqm.im.repository.ImGroupMuteRepository;
|
2026-04-28 20:11:38 +08:00
|
|
|
|
import com.xuqm.im.repository.ImMessageRepository;
|
2026-04-21 22:07:29 +08:00
|
|
|
|
import org.springframework.stereotype.Service;
|
2026-04-27 23:41:58 +08:00
|
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
2026-04-28 20:11:38 +08:00
|
|
|
|
import org.springframework.data.domain.PageRequest;
|
2026-04-21 22:07:29 +08:00
|
|
|
|
|
|
|
|
|
|
import java.time.LocalDateTime;
|
|
|
|
|
|
import java.util.ArrayList;
|
2026-04-28 20:11:38 +08:00
|
|
|
|
import java.util.LinkedHashSet;
|
2026-04-21 22:07:29 +08:00
|
|
|
|
import java.util.List;
|
2026-04-27 23:41:58 +08:00
|
|
|
|
import java.util.Optional;
|
2026-04-21 22:07:29 +08:00
|
|
|
|
import java.util.UUID;
|
|
|
|
|
|
|
|
|
|
|
|
@Service
|
|
|
|
|
|
public class ImGroupService {
|
|
|
|
|
|
|
|
|
|
|
|
private final ImGroupRepository groupRepository;
|
2026-04-27 23:41:58 +08:00
|
|
|
|
private final ImGroupMuteRepository muteRepository;
|
2026-04-28 09:45:20 +08:00
|
|
|
|
private final ImGroupJoinRequestRepository joinRequestRepository;
|
2026-04-28 20:11:38 +08:00
|
|
|
|
private final ImMessageRepository messageRepository;
|
|
|
|
|
|
private final ImClusterPublisher clusterPublisher;
|
2026-04-21 22:07:29 +08:00
|
|
|
|
private final ObjectMapper objectMapper;
|
|
|
|
|
|
|
2026-04-27 23:41:58 +08:00
|
|
|
|
public ImGroupService(ImGroupRepository groupRepository,
|
|
|
|
|
|
ImGroupMuteRepository muteRepository,
|
2026-04-28 09:45:20 +08:00
|
|
|
|
ImGroupJoinRequestRepository joinRequestRepository,
|
2026-04-28 20:11:38 +08:00
|
|
|
|
ImMessageRepository messageRepository,
|
|
|
|
|
|
ImClusterPublisher clusterPublisher,
|
2026-04-27 23:41:58 +08:00
|
|
|
|
ObjectMapper objectMapper) {
|
2026-04-21 22:07:29 +08:00
|
|
|
|
this.groupRepository = groupRepository;
|
2026-04-27 23:41:58 +08:00
|
|
|
|
this.muteRepository = muteRepository;
|
2026-04-28 09:45:20 +08:00
|
|
|
|
this.joinRequestRepository = joinRequestRepository;
|
2026-04-28 20:11:38 +08:00
|
|
|
|
this.messageRepository = messageRepository;
|
|
|
|
|
|
this.clusterPublisher = clusterPublisher;
|
2026-04-21 22:07:29 +08:00
|
|
|
|
this.objectMapper = objectMapper;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-27 23:41:58 +08:00
|
|
|
|
@Transactional
|
2026-04-28 09:45:20 +08:00
|
|
|
|
public ImGroupEntity create(String appId, String name, String creatorId, List<String> memberIds, String groupType) {
|
2026-04-21 22:07:29 +08:00
|
|
|
|
List<String> members = new ArrayList<>(memberIds);
|
|
|
|
|
|
if (!members.contains(creatorId)) members.add(creatorId);
|
|
|
|
|
|
|
|
|
|
|
|
ImGroupEntity group = new ImGroupEntity();
|
|
|
|
|
|
group.setId(UUID.randomUUID().toString());
|
|
|
|
|
|
group.setAppId(appId);
|
|
|
|
|
|
group.setName(name);
|
2026-04-28 09:45:20 +08:00
|
|
|
|
group.setGroupType(normalizeGroupType(groupType));
|
2026-04-21 22:07:29 +08:00
|
|
|
|
group.setCreatorId(creatorId);
|
|
|
|
|
|
group.setMemberIds(toJson(members));
|
|
|
|
|
|
group.setAdminIds(toJson(List.of(creatorId)));
|
2026-04-27 23:41:58 +08:00
|
|
|
|
group.setAnnouncement(null);
|
2026-04-21 22:07:29 +08:00
|
|
|
|
group.setCreatedAt(LocalDateTime.now());
|
|
|
|
|
|
return groupRepository.save(group);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-27 23:41:58 +08:00
|
|
|
|
public ImGroupEntity get(String groupId) {
|
|
|
|
|
|
return groupRepository.findById(groupId)
|
2026-04-21 22:07:29 +08:00
|
|
|
|
.orElseThrow(() -> new BusinessException(404, "群组不存在"));
|
2026-04-27 23:41:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public ImGroupEntity get(String groupId, String requesterId) {
|
|
|
|
|
|
ImGroupEntity group = get(groupId);
|
|
|
|
|
|
if (!memberIds(group).contains(requesterId) && !group.getCreatorId().equals(requesterId)) {
|
|
|
|
|
|
throw new BusinessException(403, "不在群内");
|
|
|
|
|
|
}
|
|
|
|
|
|
return group;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Transactional
|
|
|
|
|
|
public ImGroupEntity addMember(String groupId, String userId, String operatorId) {
|
|
|
|
|
|
ImGroupEntity group = get(groupId);
|
|
|
|
|
|
ensureCanManage(group, operatorId);
|
2026-04-21 22:07:29 +08:00
|
|
|
|
List<String> members = fromJson(group.getMemberIds());
|
|
|
|
|
|
if (!members.contains(userId)) {
|
|
|
|
|
|
members.add(userId);
|
|
|
|
|
|
group.setMemberIds(toJson(members));
|
|
|
|
|
|
groupRepository.save(group);
|
|
|
|
|
|
}
|
|
|
|
|
|
return group;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-28 21:05:06 +08:00
|
|
|
|
@Transactional
|
|
|
|
|
|
public ImGroupEntity addMembers(String groupId, List<String> userIds, String operatorId) {
|
|
|
|
|
|
ImGroupEntity group = get(groupId);
|
|
|
|
|
|
ensureCanManage(group, operatorId);
|
|
|
|
|
|
List<String> members = new ArrayList<>(fromJson(group.getMemberIds()));
|
|
|
|
|
|
boolean changed = false;
|
|
|
|
|
|
for (String userId : userIds == null ? List.<String>of() : userIds) {
|
|
|
|
|
|
if (userId == null || userId.isBlank()) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!members.contains(userId)) {
|
|
|
|
|
|
members.add(userId);
|
|
|
|
|
|
changed = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (changed) {
|
|
|
|
|
|
group.setMemberIds(toJson(members));
|
|
|
|
|
|
return groupRepository.save(group);
|
|
|
|
|
|
}
|
|
|
|
|
|
return group;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-27 23:41:58 +08:00
|
|
|
|
@Transactional
|
2026-04-21 22:07:29 +08:00
|
|
|
|
public ImGroupEntity removeMember(String groupId, String userId, String operatorId) {
|
2026-04-27 23:41:58 +08:00
|
|
|
|
ImGroupEntity group = get(groupId);
|
2026-04-21 22:07:29 +08:00
|
|
|
|
List<String> admins = fromJson(group.getAdminIds());
|
|
|
|
|
|
if (!admins.contains(operatorId) && !group.getCreatorId().equals(operatorId)) {
|
|
|
|
|
|
throw new BusinessException(403, "无权操作");
|
|
|
|
|
|
}
|
|
|
|
|
|
List<String> members = new ArrayList<>(fromJson(group.getMemberIds()));
|
|
|
|
|
|
members.remove(userId);
|
|
|
|
|
|
group.setMemberIds(toJson(members));
|
|
|
|
|
|
return groupRepository.save(group);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-28 21:05:06 +08:00
|
|
|
|
@Transactional
|
|
|
|
|
|
public ImGroupEntity removeMembers(String groupId, List<String> userIds, String operatorId) {
|
|
|
|
|
|
ImGroupEntity group = get(groupId);
|
|
|
|
|
|
List<String> admins = fromJson(group.getAdminIds());
|
|
|
|
|
|
if (!admins.contains(operatorId) && !group.getCreatorId().equals(operatorId)) {
|
|
|
|
|
|
throw new BusinessException(403, "无权操作");
|
|
|
|
|
|
}
|
|
|
|
|
|
List<String> members = new ArrayList<>(fromJson(group.getMemberIds()));
|
|
|
|
|
|
boolean changed = false;
|
|
|
|
|
|
for (String userId : userIds == null ? List.<String>of() : userIds) {
|
|
|
|
|
|
if (userId == null || userId.isBlank()) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
changed |= members.remove(userId);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (changed) {
|
|
|
|
|
|
group.setMemberIds(toJson(members));
|
|
|
|
|
|
return groupRepository.save(group);
|
|
|
|
|
|
}
|
|
|
|
|
|
return group;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-27 23:41:58 +08:00
|
|
|
|
@Transactional
|
|
|
|
|
|
public ImGroupEntity update(String groupId, String operatorId, String name, String announcement) {
|
|
|
|
|
|
ImGroupEntity group = get(groupId);
|
|
|
|
|
|
ensureCanManage(group, operatorId);
|
|
|
|
|
|
if (name != null && !name.isBlank()) {
|
|
|
|
|
|
group.setName(name);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (announcement != null) {
|
|
|
|
|
|
group.setAnnouncement(announcement);
|
|
|
|
|
|
}
|
|
|
|
|
|
return groupRepository.save(group);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Transactional
|
|
|
|
|
|
public ImGroupEntity setRole(String groupId, String operatorId, String userId, String role) {
|
|
|
|
|
|
ImGroupEntity group = get(groupId);
|
|
|
|
|
|
ensureCanManage(group, operatorId);
|
|
|
|
|
|
List<String> admins = new ArrayList<>(fromJson(group.getAdminIds()));
|
|
|
|
|
|
if ("ADMIN".equalsIgnoreCase(role)) {
|
|
|
|
|
|
if (!admins.contains(userId)) admins.add(userId);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
admins.remove(userId);
|
|
|
|
|
|
if (userId.equals(group.getCreatorId())) {
|
|
|
|
|
|
throw new BusinessException(403, "群主不能降级");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
group.setAdminIds(toJson(admins));
|
|
|
|
|
|
return groupRepository.save(group);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Transactional
|
|
|
|
|
|
public ImGroupEntity muteMember(String groupId, String operatorId, String userId, long minutes) {
|
|
|
|
|
|
ImGroupEntity group = get(groupId);
|
|
|
|
|
|
ensureCanManage(group, operatorId);
|
|
|
|
|
|
ImGroupMuteEntity mute = muteRepository
|
|
|
|
|
|
.findByGroupIdAndUserIdAndMutedUntilAfter(groupId, userId, LocalDateTime.now())
|
|
|
|
|
|
.orElseGet(() -> {
|
|
|
|
|
|
ImGroupMuteEntity entity = new ImGroupMuteEntity();
|
|
|
|
|
|
entity.setId(UUID.randomUUID().toString());
|
|
|
|
|
|
entity.setGroupId(groupId);
|
|
|
|
|
|
entity.setUserId(userId);
|
|
|
|
|
|
entity.setCreatedAt(LocalDateTime.now());
|
|
|
|
|
|
return entity;
|
|
|
|
|
|
});
|
|
|
|
|
|
mute.setMutedUntil(LocalDateTime.now().plusMinutes(Math.max(minutes, 0)));
|
|
|
|
|
|
mute.setUpdatedAt(LocalDateTime.now());
|
|
|
|
|
|
muteRepository.save(mute);
|
|
|
|
|
|
return group;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Transactional
|
|
|
|
|
|
public void dismiss(String groupId, String operatorId) {
|
|
|
|
|
|
ImGroupEntity group = get(groupId);
|
|
|
|
|
|
if (!group.getCreatorId().equals(operatorId)) {
|
|
|
|
|
|
throw new BusinessException(403, "只有群主可以解散群");
|
|
|
|
|
|
}
|
|
|
|
|
|
muteRepository.deleteByGroupId(groupId);
|
|
|
|
|
|
groupRepository.delete(group);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-28 09:45:20 +08:00
|
|
|
|
@Transactional
|
|
|
|
|
|
public void adminDismiss(String groupId) {
|
|
|
|
|
|
muteRepository.deleteByGroupId(groupId);
|
|
|
|
|
|
groupRepository.deleteById(groupId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-27 23:41:58 +08:00
|
|
|
|
public boolean isMemberMuted(String groupId, String userId) {
|
|
|
|
|
|
return muteRepository.findByGroupIdAndUserIdAndMutedUntilAfter(groupId, userId, LocalDateTime.now()).isPresent();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public List<String> memberIds(ImGroupEntity group) {
|
|
|
|
|
|
return fromJson(group.getMemberIds());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public List<String> adminIds(ImGroupEntity group) {
|
|
|
|
|
|
return fromJson(group.getAdminIds());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-21 22:07:29 +08:00
|
|
|
|
public List<ImGroupEntity> listByApp(String appId) {
|
|
|
|
|
|
return groupRepository.findByAppId(appId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-27 11:57:46 +08:00
|
|
|
|
public List<ImGroupEntity> listUserGroups(String appId, String userId) {
|
|
|
|
|
|
return groupRepository.findUserGroups(appId, userId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-28 09:45:20 +08:00
|
|
|
|
public List<ImGroupEntity> listPublicGroups(String appId, String keyword) {
|
|
|
|
|
|
String normalizedKeyword = keyword == null ? "" : keyword.trim().toLowerCase();
|
|
|
|
|
|
return groupRepository.findByAppId(appId).stream()
|
|
|
|
|
|
.filter(group -> "PUBLIC".equalsIgnoreCase(normalizeGroupType(group.getGroupType())))
|
|
|
|
|
|
.filter(group -> normalizedKeyword.isBlank()
|
|
|
|
|
|
|| group.getName().toLowerCase().contains(normalizedKeyword)
|
|
|
|
|
|
|| group.getId().toLowerCase().contains(normalizedKeyword))
|
|
|
|
|
|
.toList();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-28 20:11:38 +08:00
|
|
|
|
public List<ImGroupEntity> searchGroups(String appId, String keyword, int size) {
|
|
|
|
|
|
return groupRepository.searchByKeyword(appId, keyword, PageRequest.of(0, Math.max(size, 1)));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-28 09:45:20 +08:00
|
|
|
|
@Transactional
|
|
|
|
|
|
public ImGroupJoinRequestEntity sendJoinRequest(String appId, String groupId, String requesterId, String remark) {
|
|
|
|
|
|
ImGroupEntity group = get(groupId);
|
|
|
|
|
|
if (!group.getAppId().equals(appId)) {
|
|
|
|
|
|
throw new BusinessException(403, "无权操作");
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!"PUBLIC".equalsIgnoreCase(normalizeGroupType(group.getGroupType()))) {
|
|
|
|
|
|
throw new BusinessException(400, "该群不支持申请加入");
|
|
|
|
|
|
}
|
|
|
|
|
|
if (memberIds(group).contains(requesterId)) {
|
|
|
|
|
|
throw new BusinessException(400, "已经在群内");
|
|
|
|
|
|
}
|
|
|
|
|
|
return joinRequestRepository.findByAppIdAndGroupIdAndRequesterId(appId, groupId, requesterId)
|
|
|
|
|
|
.orElseGet(() -> {
|
|
|
|
|
|
ImGroupJoinRequestEntity entity = new ImGroupJoinRequestEntity();
|
|
|
|
|
|
entity.setId(UUID.randomUUID().toString());
|
|
|
|
|
|
entity.setAppId(appId);
|
|
|
|
|
|
entity.setGroupId(groupId);
|
|
|
|
|
|
entity.setRequesterId(requesterId);
|
|
|
|
|
|
entity.setRemark(remark);
|
|
|
|
|
|
entity.setStatus(ImGroupJoinRequestEntity.Status.PENDING.name());
|
|
|
|
|
|
entity.setCreatedAt(LocalDateTime.now());
|
2026-04-28 20:11:38 +08:00
|
|
|
|
ImGroupJoinRequestEntity saved = joinRequestRepository.save(entity);
|
|
|
|
|
|
publishJoinRequestNotification(
|
|
|
|
|
|
group,
|
|
|
|
|
|
requesterId,
|
|
|
|
|
|
uniqueRecipients(group),
|
|
|
|
|
|
"GROUP_JOIN_REQUEST",
|
|
|
|
|
|
"入群申请",
|
|
|
|
|
|
buildDescription("入群申请", remark),
|
|
|
|
|
|
saved
|
|
|
|
|
|
);
|
|
|
|
|
|
return saved;
|
2026-04-28 09:45:20 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public List<ImGroupJoinRequestEntity> listJoinRequests(String appId, String groupId, String operatorId) {
|
|
|
|
|
|
ImGroupEntity group = get(groupId);
|
|
|
|
|
|
ensureCanManage(group, operatorId);
|
|
|
|
|
|
return joinRequestRepository.findByAppIdAndGroupId(appId, groupId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Transactional
|
|
|
|
|
|
public ImGroupJoinRequestEntity acceptJoinRequest(String appId, String requestId, String operatorId) {
|
|
|
|
|
|
ImGroupJoinRequestEntity request = getJoinRequest(appId, requestId);
|
|
|
|
|
|
ImGroupEntity group = get(request.getGroupId());
|
|
|
|
|
|
ensureCanManage(group, operatorId);
|
|
|
|
|
|
request.setStatus(ImGroupJoinRequestEntity.Status.ACCEPTED.name());
|
|
|
|
|
|
request.setReviewedAt(LocalDateTime.now());
|
2026-04-28 20:11:38 +08:00
|
|
|
|
ImGroupJoinRequestEntity saved = joinRequestRepository.save(request);
|
2026-04-28 09:45:20 +08:00
|
|
|
|
addMemberInternal(group, request.getRequesterId());
|
2026-04-28 20:11:38 +08:00
|
|
|
|
publishJoinRequestNotification(
|
|
|
|
|
|
group,
|
|
|
|
|
|
operatorId,
|
|
|
|
|
|
List.of(request.getRequesterId()),
|
|
|
|
|
|
"GROUP_JOIN_REQUEST_STATUS",
|
|
|
|
|
|
"入群申请已通过",
|
|
|
|
|
|
buildDescription("入群申请已通过", null),
|
|
|
|
|
|
saved
|
|
|
|
|
|
);
|
|
|
|
|
|
return saved;
|
2026-04-28 09:45:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Transactional
|
|
|
|
|
|
public ImGroupJoinRequestEntity rejectJoinRequest(String appId, String requestId, String operatorId) {
|
|
|
|
|
|
ImGroupJoinRequestEntity request = getJoinRequest(appId, requestId);
|
|
|
|
|
|
ImGroupEntity group = get(request.getGroupId());
|
|
|
|
|
|
ensureCanManage(group, operatorId);
|
|
|
|
|
|
request.setStatus(ImGroupJoinRequestEntity.Status.REJECTED.name());
|
|
|
|
|
|
request.setReviewedAt(LocalDateTime.now());
|
2026-04-28 20:11:38 +08:00
|
|
|
|
ImGroupJoinRequestEntity saved = joinRequestRepository.save(request);
|
|
|
|
|
|
publishJoinRequestNotification(
|
|
|
|
|
|
group,
|
|
|
|
|
|
operatorId,
|
|
|
|
|
|
List.of(request.getRequesterId()),
|
|
|
|
|
|
"GROUP_JOIN_REQUEST_STATUS",
|
|
|
|
|
|
"入群申请已拒绝",
|
|
|
|
|
|
buildDescription("入群申请已拒绝", null),
|
|
|
|
|
|
saved
|
|
|
|
|
|
);
|
|
|
|
|
|
return saved;
|
2026-04-28 09:45:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-28 21:05:06 +08:00
|
|
|
|
@Transactional
|
|
|
|
|
|
public List<ImGroupJoinRequestEntity> acceptJoinRequests(String appId, String groupId, List<String> requestIds, String operatorId) {
|
|
|
|
|
|
ImGroupEntity group = get(groupId);
|
|
|
|
|
|
ensureCanManage(group, operatorId);
|
|
|
|
|
|
List<ImGroupJoinRequestEntity> result = new ArrayList<>();
|
|
|
|
|
|
for (String requestId : unique(requestIds)) {
|
|
|
|
|
|
result.add(acceptJoinRequestInternal(appId, group, requestId, operatorId));
|
|
|
|
|
|
}
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Transactional
|
|
|
|
|
|
public List<ImGroupJoinRequestEntity> rejectJoinRequests(String appId, String groupId, List<String> requestIds, String operatorId) {
|
|
|
|
|
|
ImGroupEntity group = get(groupId);
|
|
|
|
|
|
ensureCanManage(group, operatorId);
|
|
|
|
|
|
List<ImGroupJoinRequestEntity> result = new ArrayList<>();
|
|
|
|
|
|
for (String requestId : unique(requestIds)) {
|
|
|
|
|
|
result.add(rejectJoinRequestInternal(appId, group, requestId, operatorId));
|
|
|
|
|
|
}
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-21 22:07:29 +08:00
|
|
|
|
private String toJson(List<String> list) {
|
|
|
|
|
|
try { return objectMapper.writeValueAsString(list); } catch (Exception e) { return "[]"; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private List<String> fromJson(String json) {
|
|
|
|
|
|
try { return objectMapper.readValue(json, new TypeReference<>() {}); } catch (Exception e) { return new ArrayList<>(); }
|
|
|
|
|
|
}
|
2026-04-27 23:41:58 +08:00
|
|
|
|
|
|
|
|
|
|
private void ensureCanManage(ImGroupEntity group, String operatorId) {
|
|
|
|
|
|
List<String> admins = fromJson(group.getAdminIds());
|
|
|
|
|
|
if (!admins.contains(operatorId) && !group.getCreatorId().equals(operatorId)) {
|
|
|
|
|
|
throw new BusinessException(403, "无权操作");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-04-28 09:45:20 +08:00
|
|
|
|
|
2026-04-28 20:11:38 +08:00
|
|
|
|
private List<String> uniqueRecipients(ImGroupEntity group) {
|
|
|
|
|
|
LinkedHashSet<String> recipients = new LinkedHashSet<>(fromJson(group.getAdminIds()));
|
|
|
|
|
|
recipients.add(group.getCreatorId());
|
|
|
|
|
|
return new ArrayList<>(recipients);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void publishJoinRequestNotification(
|
|
|
|
|
|
ImGroupEntity group,
|
|
|
|
|
|
String fromUserId,
|
|
|
|
|
|
List<String> recipients,
|
|
|
|
|
|
String type,
|
|
|
|
|
|
String title,
|
|
|
|
|
|
String content,
|
|
|
|
|
|
ImGroupJoinRequestEntity request
|
|
|
|
|
|
) {
|
|
|
|
|
|
for (String recipient : recipients) {
|
|
|
|
|
|
if (recipient == null || recipient.isBlank() || recipient.equals(fromUserId)) continue;
|
|
|
|
|
|
ImMessageEntity message = new ImMessageEntity();
|
|
|
|
|
|
message.setId(UUID.randomUUID().toString());
|
|
|
|
|
|
message.setAppId(group.getAppId());
|
|
|
|
|
|
message.setFromUserId(fromUserId);
|
|
|
|
|
|
message.setToId(recipient);
|
|
|
|
|
|
message.setChatType(ImMessageEntity.ChatType.SINGLE);
|
|
|
|
|
|
message.setMsgType(ImMessageEntity.MsgType.NOTIFY);
|
|
|
|
|
|
message.setContent(buildNotificationContent(type, title, content, request, group));
|
|
|
|
|
|
message.setStatus(ImMessageEntity.MsgStatus.SENT);
|
|
|
|
|
|
message.setCreatedAt(LocalDateTime.now());
|
|
|
|
|
|
ImMessageEntity saved = messageRepository.save(message);
|
|
|
|
|
|
clusterPublisher.publish("/user/" + recipient + "/queue/messages", saved);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private String buildNotificationContent(
|
|
|
|
|
|
String type,
|
|
|
|
|
|
String title,
|
|
|
|
|
|
String content,
|
|
|
|
|
|
ImGroupJoinRequestEntity request,
|
|
|
|
|
|
ImGroupEntity group
|
|
|
|
|
|
) {
|
|
|
|
|
|
ObjectNode node = objectMapper.createObjectNode();
|
|
|
|
|
|
node.put("type", type);
|
|
|
|
|
|
node.put("title", title);
|
|
|
|
|
|
node.put("content", content);
|
|
|
|
|
|
node.put("requestId", request.getId());
|
|
|
|
|
|
node.put("groupId", request.getGroupId());
|
|
|
|
|
|
node.put("groupName", group.getName());
|
|
|
|
|
|
node.put("requesterId", request.getRequesterId());
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-28 09:45:20 +08:00
|
|
|
|
private void addMemberInternal(ImGroupEntity group, String userId) {
|
|
|
|
|
|
List<String> members = fromJson(group.getMemberIds());
|
|
|
|
|
|
if (!members.contains(userId)) {
|
|
|
|
|
|
members.add(userId);
|
|
|
|
|
|
group.setMemberIds(toJson(members));
|
|
|
|
|
|
groupRepository.save(group);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private ImGroupJoinRequestEntity getJoinRequest(String appId, String requestId) {
|
|
|
|
|
|
ImGroupJoinRequestEntity request = joinRequestRepository.findById(requestId)
|
|
|
|
|
|
.orElseThrow(() -> new BusinessException(404, "加群申请不存在"));
|
|
|
|
|
|
if (!request.getAppId().equals(appId)) {
|
|
|
|
|
|
throw new BusinessException(403, "无权操作");
|
|
|
|
|
|
}
|
|
|
|
|
|
return request;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-28 21:05:06 +08:00
|
|
|
|
private ImGroupJoinRequestEntity acceptJoinRequestInternal(String appId, ImGroupEntity group, String requestId, String operatorId) {
|
|
|
|
|
|
ImGroupJoinRequestEntity request = getJoinRequest(appId, requestId);
|
|
|
|
|
|
if (!group.getId().equals(request.getGroupId())) {
|
|
|
|
|
|
throw new BusinessException(400, "加群申请不属于当前群");
|
|
|
|
|
|
}
|
|
|
|
|
|
ensureCanManage(group, operatorId);
|
|
|
|
|
|
request.setStatus(ImGroupJoinRequestEntity.Status.ACCEPTED.name());
|
|
|
|
|
|
request.setReviewedAt(LocalDateTime.now());
|
|
|
|
|
|
ImGroupJoinRequestEntity saved = joinRequestRepository.save(request);
|
|
|
|
|
|
addMemberInternal(group, request.getRequesterId());
|
|
|
|
|
|
publishJoinRequestNotification(
|
|
|
|
|
|
group,
|
|
|
|
|
|
operatorId,
|
|
|
|
|
|
List.of(request.getRequesterId()),
|
|
|
|
|
|
"GROUP_JOIN_REQUEST_STATUS",
|
|
|
|
|
|
"入群申请已通过",
|
|
|
|
|
|
buildDescription("入群申请已通过", null),
|
|
|
|
|
|
saved
|
|
|
|
|
|
);
|
|
|
|
|
|
return saved;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private ImGroupJoinRequestEntity rejectJoinRequestInternal(String appId, ImGroupEntity group, String requestId, String operatorId) {
|
|
|
|
|
|
ImGroupJoinRequestEntity request = getJoinRequest(appId, requestId);
|
|
|
|
|
|
if (!group.getId().equals(request.getGroupId())) {
|
|
|
|
|
|
throw new BusinessException(400, "加群申请不属于当前群");
|
|
|
|
|
|
}
|
|
|
|
|
|
ensureCanManage(group, operatorId);
|
|
|
|
|
|
request.setStatus(ImGroupJoinRequestEntity.Status.REJECTED.name());
|
|
|
|
|
|
request.setReviewedAt(LocalDateTime.now());
|
|
|
|
|
|
ImGroupJoinRequestEntity saved = joinRequestRepository.save(request);
|
|
|
|
|
|
publishJoinRequestNotification(
|
|
|
|
|
|
group,
|
|
|
|
|
|
operatorId,
|
|
|
|
|
|
List.of(request.getRequesterId()),
|
|
|
|
|
|
"GROUP_JOIN_REQUEST_STATUS",
|
|
|
|
|
|
"入群申请已拒绝",
|
|
|
|
|
|
buildDescription("入群申请已拒绝", null),
|
|
|
|
|
|
saved
|
|
|
|
|
|
);
|
|
|
|
|
|
return saved;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-28 09:45:20 +08:00
|
|
|
|
private String normalizeGroupType(String groupType) {
|
|
|
|
|
|
return (groupType == null || groupType.isBlank()) ? "WORK" : groupType.trim().toUpperCase();
|
|
|
|
|
|
}
|
2026-04-28 21:05:06 +08:00
|
|
|
|
|
|
|
|
|
|
private List<String> unique(List<String> values) {
|
|
|
|
|
|
return values == null ? List.of() : new ArrayList<>(new LinkedHashSet<>(values));
|
|
|
|
|
|
}
|
2026-04-21 22:07:29 +08:00
|
|
|
|
}
|