ChatController
package com.multi.backend5_1_multi_fc.chat.controller;
import com.multi.backend5_1_multi_fc.chat.dto.ChatMessageDto;
import com.multi.backend5_1_multi_fc.chat.dto.ChatParticipantDto;
import com.multi.backend5_1_multi_fc.chat.dto.ChatRoomDto;
import com.multi.backend5_1_multi_fc.chat.dto.ChatRoomWithParticipantDto;
import com.multi.backend5_1_multi_fc.chat.service.ChatService;
import com.multi.backend5_1_multi_fc.security.CustomUserDetails;
import lombok.RequiredArgsConstructor;
import org.springframework.messaging.handler.annotation.DestinationVariable;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequiredArgsConstructor
public class ChatController {
private final ChatService chatService;
@PostMapping("/chatroom/onetoone")
public ChatRoomDto createOneToOneChatRoom(
@RequestParam("targetUserId") Long targetUserId,
@AuthenticationPrincipal CustomUserDetails userDetails) {
return chatService.createOrGetOneToOneChatRoom(userDetails.getUserId(), targetUserId);
}
//1. 그룹 채팅방 생성
@PostMapping("/chatroom/group")
public void createChatRoom(@RequestBody ChatRoomDto chatRoomDto){
chatService.createChatRoom(chatRoomDto);
}
//사용자의 채팅방 목록 조회 (타입별) - 동적 이름 포함
@GetMapping("/chatroom")
public List<ChatRoomWithParticipantDto> getChatRooms(
@RequestParam("type") String roomType,
@AuthenticationPrincipal CustomUserDetails userDetails){
return chatService.getUserChatRoomsByType(userDetails.getUserId(), roomType);
}
//2. 채팅방 단일 조회 (방의 정보를 응답받는 형태 - 방 제목, 멤버 )
@GetMapping("/chatroom/{id}")
public ChatRoomDto getChatRoom(@PathVariable("id") Long roomId){
return chatService.findChatRoomById(roomId);
}
//3. 채팅방 메세지 목록
@GetMapping("/chatroom/{id}/messages")
public List<ChatMessageDto> getChatMessages(@PathVariable("id") Long roomId){
return chatService.getMessagesByRoomId(roomId);
}
//4. 채팅방 참가자 목록
@GetMapping("/chatroom/{id}/participants")
public List<ChatParticipantDto> getChatParticipants(@PathVariable("id") Long roomId){
return chatService.getParticipantsByRoomId(roomId);
}
//5. 채팅방 참가자 추가
@PostMapping("/chatroom/{id}}/invite")
public void inviteParticipant(@PathVariable("id") Long roomId, @RequestBody ChatParticipantDto participant){
participant.setRoomId(roomId);
chatService.addParticipant(participant);
}
//채팅 전송 기능
@MessageMapping("/chatroom/{roomId}/send")
public void sendMessage(@DestinationVariable Long roomId, @Payload ChatMessageDto messageDto, SimpMessageHeaderAccessor headerAccessor){
messageDto.setRoomId(roomId);
chatService.sendMessage(messageDto);
}
}
ChatRoomDao
package com.multi.backend5_1_multi_fc.chat.dao;
import com.multi.backend5_1_multi_fc.chat.dto.ChatRoomDto;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface ChatRoomDao {
List<ChatRoomDto> findChatRoomsByUserIdAndType(@Param("userId") Long userId,@Param("roomType") String roomType);
ChatRoomDto findOneToOneChatRoom(@Param("userId1") Long userId1, @Param("userId2") Long userId2);
ChatRoomDto findChatRoomById(Long roomId);
void insertChatRoom(ChatRoomDto chatRoom);
void updateMemberCount(Long roomId, int memberCount);
void deleteChatRoom(Long roomId);
Long getLastRoomId();
}
ChatParticipantDto
package com.multi.backend5_1_multi_fc.chat.dto;
import lombok.Builder;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@Builder
public class ChatParticipantDto {
//채팅방 참가 정보
private Long chatPartId;
private Long roomId;
private Long userId;
private String nickname;
private String profileImg;
private LocalDateTime joinedAt;
}
ChatRoomDto
package com.multi.backend5_1_multi_fc.chat.dto;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class ChatRoomDto {
private Long roomId;
private String roomType;
private String roomName;
private int memberCount;
}
ChatRoomWithParticipantDto
package com.multi.backend5_1_multi_fc.chat.dto;
import lombok.Data;
@Data
public class ChatRoomWithParticipantDto {
//채팅방 정보 + 추가 정보를 담는 응답용 Dto
private Long roomId;
private String roomType;
private String roomName;
private int memberCount;
private int unreadCount;
}
ChatService
package com.multi.backend5_1_multi_fc.chat.service;
import com.multi.backend5_1_multi_fc.chat.dao.ChatMessageDao;
import com.multi.backend5_1_multi_fc.chat.dao.ChatParticipantDao;
import com.multi.backend5_1_multi_fc.chat.dao.ChatRoomDao;
import com.multi.backend5_1_multi_fc.chat.dto.ChatMessageDto;
import com.multi.backend5_1_multi_fc.chat.dto.ChatParticipantDto;
import com.multi.backend5_1_multi_fc.chat.dto.ChatRoomDto;
import com.multi.backend5_1_multi_fc.chat.dto.ChatRoomWithParticipantDto;
import com.multi.backend5_1_multi_fc.notification.service.NotificationService;
import com.multi.backend5_1_multi_fc.user.dao.UserDao;
import com.multi.backend5_1_multi_fc.user.dto.UserDto;
import lombok.RequiredArgsConstructor;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class ChatService {
private final ChatRoomDao chatRoomDao;
private final ChatMessageDao chatMessageDao;
private final ChatParticipantDao chatParticipantDao;
private final UserDao userDao;
private final RabbitTemplate rabbitTemplate;
private final NotificationService notificationService;
public List<ChatRoomWithParticipantDto> getUserChatRoomsByType(Long userId, String roomType){
List<ChatRoomDto> rooms = chatRoomDao.findChatRoomsByUserIdAndType(userId, roomType);
return rooms.stream().map(room -> {
ChatRoomWithParticipantDto result = new ChatRoomWithParticipantDto();
result.setRoomId(room.getRoomId());
result.setRoomType(room.getRoomType());
result.setMemberCount(room.getMemberCount());
if("1대1".equals(room.getRoomType())){
String dynamicRoomName = generateOneToOneRoomName(room.getRoomId(), userId);
result.setRoomName(dynamicRoomName);
} else {
result.setRoomName(room.getRoomName());
}
return result;
}).collect(Collectors.toList());
}
//1대1 채팅방 이름 동적 생성(상대방 닉네임)
private String generateOneToOneRoomName(Long roomId, Long currentUserId){
List<ChatParticipantDto> participants = chatParticipantDao.findParticipantsByRoomId(roomId);
ChatParticipantDto opponent = participants.stream()
.filter(p -> !p.getUserId().equals(currentUserId))
.findFirst()
.orElse(null);
if(opponent != null){
UserDto opponentUser = userDao.findByUserId(opponent.getUserId());
return opponentUser.getNickname() + "님과의 채팅";
}
return "1대 1 채팅";
}
//채팅방 생성 (그룹 채팅용)
public void createChatRoom(ChatRoomDto chatRoomDto){
chatRoomDao.insertChatRoom(chatRoomDto);
}
//채팅방 조회
public ChatRoomDto findChatRoomById(Long roomId){
return chatRoomDao.findChatRoomById(roomId);
}
//1대 1 채팅방 버튼
public ChatRoomDto createOrGetOneToOneChatRoom(Long userId1, Long userId2){
ChatRoomDto existingRoom = chatRoomDao.findOneToOneChatRoom(userId1, userId2);
Long currentLastRoom = chatRoomDao.getLastRoomId();
if(existingRoom != null){
return existingRoom;
}
UserDto targetUser = userDao.findByUserId(userId2);
ChatRoomDto newRoom = ChatRoomDto.builder()
.roomId(currentLastRoom + 1)
.roomType("1대1")
.roomName(targetUser.getNickname() + " 님과의 채팅")
.memberCount(2)
.build();
chatRoomDao.insertChatRoom(newRoom);
chatParticipantDao.insertParticipant(ChatParticipantDto.builder()
.roomId(newRoom.getRoomId())
.userId(userId1)
.build()
);
chatParticipantDao.insertParticipant(ChatParticipantDto.builder()
.roomId(newRoom.getRoomId())
.userId(userId2)
.build()
);
return newRoom;
}
//채팅 메세지 저장 + MQ publish
public void sendMessage(ChatMessageDto messageDto){
//DB 메세지 저장
chatMessageDao.insertMessage(messageDto);
//RabbitMQ로 publish
rabbitTemplate.convertAndSend("chat.exchange", "room." + messageDto.getRoomId(), messageDto);
ChatRoomDto chatRoom = chatRoomDao.findChatRoomById(messageDto.getRoomId());
List<ChatParticipantDto> participants = chatParticipantDao.findParticipantsByRoomId(messageDto.getRoomId());
for(ChatParticipantDto participant : participants){
if(!participant.getUserId().equals(messageDto.getSenderId())){
String notificationRoomName;
if("1대1".equals(chatRoom.getRoomType())){
notificationRoomName = generateOneToOneRoomName(messageDto.getRoomId(), participant.getUserId());
} else {
notificationRoomName = chatRoom.getRoomName();
}
notificationService.createOrUpdateChatNotification(
participant.getUserId(),
notificationRoomName,
messageDto.getRoomId()
);
}
}
}
//채팅방 메세지 목록 조회
public List<ChatMessageDto> getMessagesByRoomId(Long roomId){
return chatMessageDao.findMessagesByRoomId(roomId);
}
//채팅방 참가자 목록 조회
public List<ChatParticipantDto> getParticipantsByRoomId(Long roomId){
return chatParticipantDao.findParticipantsByRoomId(roomId);
}
//채팅방 참가자 추가
public void addParticipant(ChatParticipantDto participant){
chatParticipantDao.insertParticipant(participant);
//초대받은 사용자에게 알림
ChatRoomDto chatRoom = chatRoomDao.findChatRoomById(participant.getRoomId());
notificationService.createAndSendNotification(
participant.getUserId(),
chatRoom.getRoomName() + "채팅방에 초대되었습니다.",
"채팅",
participant.getRoomId()
);
}
//채팅방 참가자 퇴장
public void removeParticipant(Long roomId, Long chatPartId){
chatParticipantDao.deleteParticipant(roomId,chatPartId);
}
}
ChatRoomMapper