package com.xuqm.im.service; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.xuqm.common.exception.BusinessException; import com.xuqm.im.entity.ImGroupEntity; import com.xuqm.im.entity.ImGroupJoinRequestEntity; import com.xuqm.im.entity.ImGroupMuteEntity; import com.xuqm.im.repository.ImGroupJoinRequestRepository; import com.xuqm.im.repository.ImGroupRepository; import com.xuqm.im.repository.ImGroupMuteRepository; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.UUID; @Service public class ImGroupService { private final ImGroupRepository groupRepository; private final ImGroupMuteRepository muteRepository; private final ImGroupJoinRequestRepository joinRequestRepository; private final ObjectMapper objectMapper; public ImGroupService(ImGroupRepository groupRepository, ImGroupMuteRepository muteRepository, ImGroupJoinRequestRepository joinRequestRepository, ObjectMapper objectMapper) { this.groupRepository = groupRepository; this.muteRepository = muteRepository; this.joinRequestRepository = joinRequestRepository; this.objectMapper = objectMapper; } @Transactional public ImGroupEntity create(String appId, String name, String creatorId, List memberIds, String groupType) { List 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); group.setGroupType(normalizeGroupType(groupType)); group.setCreatorId(creatorId); group.setMemberIds(toJson(members)); group.setAdminIds(toJson(List.of(creatorId))); group.setAnnouncement(null); group.setCreatedAt(LocalDateTime.now()); return groupRepository.save(group); } public ImGroupEntity get(String groupId) { return groupRepository.findById(groupId) .orElseThrow(() -> new BusinessException(404, "群组不存在")); } 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); List members = fromJson(group.getMemberIds()); if (!members.contains(userId)) { members.add(userId); group.setMemberIds(toJson(members)); groupRepository.save(group); } return group; } @Transactional public ImGroupEntity removeMember(String groupId, String userId, String operatorId) { ImGroupEntity group = get(groupId); List admins = fromJson(group.getAdminIds()); if (!admins.contains(operatorId) && !group.getCreatorId().equals(operatorId)) { throw new BusinessException(403, "无权操作"); } List members = new ArrayList<>(fromJson(group.getMemberIds())); members.remove(userId); group.setMemberIds(toJson(members)); return groupRepository.save(group); } @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 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); } @Transactional public void adminDismiss(String groupId) { muteRepository.deleteByGroupId(groupId); groupRepository.deleteById(groupId); } public boolean isMemberMuted(String groupId, String userId) { return muteRepository.findByGroupIdAndUserIdAndMutedUntilAfter(groupId, userId, LocalDateTime.now()).isPresent(); } public List memberIds(ImGroupEntity group) { return fromJson(group.getMemberIds()); } public List adminIds(ImGroupEntity group) { return fromJson(group.getAdminIds()); } public List listByApp(String appId) { return groupRepository.findByAppId(appId); } public List listUserGroups(String appId, String userId) { return groupRepository.findUserGroups(appId, userId); } public List 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(); } @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()); return joinRequestRepository.save(entity); }); } public List 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()); joinRequestRepository.save(request); addMemberInternal(group, request.getRequesterId()); return request; } @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()); return joinRequestRepository.save(request); } private String toJson(List list) { try { return objectMapper.writeValueAsString(list); } catch (Exception e) { return "[]"; } } private List fromJson(String json) { try { return objectMapper.readValue(json, new TypeReference<>() {}); } catch (Exception e) { return new ArrayList<>(); } } private void ensureCanManage(ImGroupEntity group, String operatorId) { List admins = fromJson(group.getAdminIds()); if (!admins.contains(operatorId) && !group.getCreatorId().equals(operatorId)) { throw new BusinessException(403, "无权操作"); } } private void addMemberInternal(ImGroupEntity group, String userId) { List 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; } private String normalizeGroupType(String groupType) { return (groupType == null || groupType.isBlank()) ? "WORK" : groupType.trim().toUpperCase(); } }