diff --git a/.github/workflows/backend-dev-cd.yml b/.github/workflows/backend-dev-cd.yml index 3ead92e3..99605015 100644 --- a/.github/workflows/backend-dev-cd.yml +++ b/.github/workflows/backend-dev-cd.yml @@ -64,7 +64,7 @@ jobs: - name: 도커 허브 리포지토리에서 최신 이미지 가져오기 run: | sudo docker login --username ${{ secrets.DEV_DOCKER_HUB_USERNAME }} --password ${{ secrets.DEV_DOCKER_HUB_ACCESS_TOKEN }} - sudo docker pull ${{ secrets.DEV_DOCKER_HUB_REPOSITORY }}:${{ secrets.IMAGE_TAG }} + sudo docker pull ${{ secrets.DEV_DOCKER_HUB_REPOSITORY }}:${{ secrets.DEV_IMAGE_TAG }} - name: blue green 배포 run: | diff --git a/docker/init.js b/docker/init.js index 872da0a2..9cd89942 100644 --- a/docker/init.js +++ b/docker/init.js @@ -1,2 +1,3 @@ db = db.getSiblingDB('dao-local-db'); db.createCollection('chat_message'); +db.createCollection('chat_user'); diff --git a/src/main/java/com/example/daobe/auth/application/AuthService.java b/src/main/java/com/example/daobe/auth/application/AuthService.java index 0bf972d3..ba168dea 100644 --- a/src/main/java/com/example/daobe/auth/application/AuthService.java +++ b/src/main/java/com/example/daobe/auth/application/AuthService.java @@ -8,12 +8,13 @@ import com.example.daobe.auth.domain.Token; import com.example.daobe.auth.domain.repository.TokenRepository; import com.example.daobe.auth.exception.AuthException; -import com.example.daobe.objet.domain.repository.ObjetRepository; import com.example.daobe.user.domain.User; +import com.example.daobe.user.domain.event.UserCreateEvent; import com.example.daobe.user.domain.repository.UserRepository; import com.example.daobe.user.exception.UserException; import com.example.daobe.user.exception.UserExceptionType; import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; @Service @@ -24,11 +25,11 @@ public class AuthService { private final TokenExtractor tokenExtractor; private final UserRepository userRepository; private final TokenRepository tokenRepository; - private final ObjetRepository objetRepository; + private final ApplicationEventPublisher eventPublisher; public TokenResponseDto loginOrRegister(String oAuthId) { User findUser = userRepository.findByKakaoId(oAuthId) - .orElseGet(() -> userRepository.save(generatedUser(oAuthId))); + .orElseGet(() -> saveAndPublishEvent(oAuthId)); Token newToken = Token.builder() .memberId(findUser.getId()) @@ -40,10 +41,6 @@ public TokenResponseDto loginOrRegister(String oAuthId) { return TokenResponseDto.of(accessToken, refreshToken); } - private User generatedUser(String oAuthId) { - return User.builder().kakaoId(oAuthId).build(); - } - public TokenResponseDto reissueTokenPair(String currentToken) { String tokenId = tokenExtractor.extractRefreshToken(currentToken); Token findToken = tokenRepository.findByTokenId(tokenId) @@ -89,4 +86,10 @@ public void withdraw(Long userId, String currentToken, WithdrawRequestDto reques findUser.withdrawWithAddReason(request.reasonTypeList(), request.detail()); userRepository.save(findUser); } + + private User saveAndPublishEvent(String oAuthId) { + User newUser = userRepository.save(User.builder().kakaoId(oAuthId).build()); + eventPublisher.publishEvent(UserCreateEvent.of(newUser)); + return newUser; + } } diff --git a/src/main/java/com/example/daobe/chat/application/ChatService.java b/src/main/java/com/example/daobe/chat/application/ChatService.java index e30e89e1..61f2c120 100644 --- a/src/main/java/com/example/daobe/chat/application/ChatService.java +++ b/src/main/java/com/example/daobe/chat/application/ChatService.java @@ -11,15 +11,21 @@ import com.example.daobe.chat.application.dto.ChatRoomTokenDto; import com.example.daobe.chat.domain.ChatMessage; import com.example.daobe.chat.domain.ChatRoom; +import com.example.daobe.chat.domain.ChatUser; import com.example.daobe.chat.domain.repository.ChatMessageRepository; import com.example.daobe.chat.domain.repository.ChatRoomRepository; +import com.example.daobe.chat.domain.repository.ChatUserRepository; import com.example.daobe.chat.exception.ChatException; import com.example.daobe.user.application.UserService; import com.example.daobe.user.application.dto.UserInfoResponseDto; import java.time.LocalDateTime; import java.util.Comparator; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.messaging.simp.SimpMessageHeaderAccessor; @@ -40,6 +46,7 @@ public class ChatService { private final UserService userService; private final ChatRoomRepository chatRoomRepository; + private final ChatUserRepository chatUserRepository; private final ChatMessageRepository chatMessageRepository; @Transactional @@ -78,32 +85,44 @@ public EnterAndLeaveMessage createEnterMessageAndSetSessionAttribute( } public ChatMessageDto createMessage(ChatMessageDto message, String roomToken) { - Long senderId = message.senderId(); - UserInfoResponseDto findUser = userService.getUserInfoWithId(senderId); + ChatUser findUser = chatUserRepository.findByUserId(message.senderId()) + .orElseThrow(() -> new RuntimeException("NOT_EXISTS_USER_EXCEPTION")); ChatMessage chatMessage = ChatMessage.builder() .type(TALK) .roomToken(roomToken) - .senderId(findUser.userId()) - .sender(findUser.nickname()) - .senderProfileUrl(findUser.profileUrl()) + .senderId(findUser.getUserId()) .message(message.message()) .build(); // MongoDB 저장 ChatMessage savedChatMessage = chatMessageRepository.save(chatMessage); - return ChatMessageDto.of(savedChatMessage); + return ChatMessageDto.of(savedChatMessage, findUser); } public List getMessagesByRoomToken(String roomToken, boolean isAll) { + List chatMessages; if (isAll) { - return chatMessageRepository.findAllByRoomToken(roomToken).stream() - .map(ChatMessageDto::of) + chatMessages = chatMessageRepository.findAllByRoomToken(roomToken); + } else { + chatMessages = chatMessageRepository.findAllByRoomTokenOrderByCreatedAtDesc(roomToken) + .stream() + .limit(RECENT_MESSAGES_COUNT) .toList(); } - return chatMessageRepository.findAllByRoomTokenOrderByCreatedAtDesc(roomToken).stream() - .limit(RECENT_MESSAGES_COUNT) - .map(ChatMessageDto::of) + + // TODO: Redis 활용한 캐싱 처리 필요 + Set userIds = chatMessages.stream() + .map(ChatMessage::getSenderId) + .collect(Collectors.toSet()); + List chatUsers = chatUserRepository.findAllByUserIdIn(userIds); + Map userInfos = chatUsers.stream() + .collect(Collectors.toMap(ChatUser::getUserId, Function.identity())); + return chatMessages.stream() + .map(chatMessage -> { + ChatUser chatUser = userInfos.get(chatMessage.getSenderId()); + return ChatMessageDto.of(chatMessage, chatUser); + }) .sorted(Comparator.comparing(ChatMessageDto::createdAt)) .toList(); } diff --git a/src/main/java/com/example/daobe/chat/application/dto/ChatMessageDto.java b/src/main/java/com/example/daobe/chat/application/dto/ChatMessageDto.java index ea1bbbb7..1535ee47 100644 --- a/src/main/java/com/example/daobe/chat/application/dto/ChatMessageDto.java +++ b/src/main/java/com/example/daobe/chat/application/dto/ChatMessageDto.java @@ -1,6 +1,7 @@ package com.example.daobe.chat.application.dto; import com.example.daobe.chat.domain.ChatMessage; +import com.example.daobe.chat.domain.ChatUser; import java.time.LocalDateTime; public record ChatMessageDto( @@ -13,14 +14,14 @@ public record ChatMessageDto( String message, LocalDateTime createdAt ) { - public static ChatMessageDto of(ChatMessage message) { + public static ChatMessageDto of(ChatMessage message, ChatUser sender) { return new ChatMessageDto( message.getId(), message.getType(), message.getRoomToken(), message.getSenderId(), - message.getSender(), - message.getSenderProfileUrl(), + sender.getNickname(), + sender.getProfileUrl(), message.getMessage(), message.getCreatedAt() ); diff --git a/src/main/java/com/example/daobe/chat/application/event/ChatUserEventListener.java b/src/main/java/com/example/daobe/chat/application/event/ChatUserEventListener.java new file mode 100644 index 00000000..87eb4e3b --- /dev/null +++ b/src/main/java/com/example/daobe/chat/application/event/ChatUserEventListener.java @@ -0,0 +1,36 @@ +package com.example.daobe.chat.application.event; + +import com.example.daobe.chat.domain.ChatUser; +import com.example.daobe.chat.domain.repository.ChatUserRepository; +import com.example.daobe.user.domain.event.UserCreateEvent; +import com.example.daobe.user.domain.event.UserUpdateEvent; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class ChatUserEventListener { + + private final ChatUserRepository chatUserRepository; + + @EventListener + public void handleUserCreated(UserCreateEvent event) { + ChatUser chatUser = ChatUser.builder() + .userId(event.userId()) + .nickname(event.nickname()) + .profileUrl(event.profileUrl()) + .build(); + chatUserRepository.save(chatUser); + } + + @EventListener + public void handleUserUpdated(UserUpdateEvent event) { + ChatUser findUser = chatUserRepository.findByUserId(event.userId()) + .orElseThrow(() -> new RuntimeException("NOT_EXISTS_USER_EXCEPTION")); + findUser.updateUserInfo(event.nickname(), event.profileUrl()); + chatUserRepository.save(findUser); + } +} diff --git a/src/main/java/com/example/daobe/chat/domain/ChatMessage.java b/src/main/java/com/example/daobe/chat/domain/ChatMessage.java index 447a57ca..a281cdb0 100644 --- a/src/main/java/com/example/daobe/chat/domain/ChatMessage.java +++ b/src/main/java/com/example/daobe/chat/domain/ChatMessage.java @@ -34,24 +34,15 @@ public class ChatMessage { @Field("sender_id") private Long senderId; - @Field("sender") - private String sender; - - @Field("sender_profile_url") - private String senderProfileUrl; - @Field("created_at") @CreatedDate private LocalDateTime createdAt; @Builder - public ChatMessage(String roomToken, MessageType type, String message, Long senderId, String sender, - String senderProfileUrl) { + public ChatMessage(String roomToken, MessageType type, String message, Long senderId) { this.roomToken = roomToken; this.type = type.name(); this.message = message; this.senderId = senderId; - this.sender = sender; - this.senderProfileUrl = senderProfileUrl; } } diff --git a/src/main/java/com/example/daobe/chat/domain/ChatUser.java b/src/main/java/com/example/daobe/chat/domain/ChatUser.java new file mode 100644 index 00000000..329ebc3c --- /dev/null +++ b/src/main/java/com/example/daobe/chat/domain/ChatUser.java @@ -0,0 +1,45 @@ +package com.example.daobe.chat.domain; + +import jakarta.persistence.Id; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.mongodb.core.index.Indexed; +import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.data.mongodb.core.mapping.Field; + +@Getter +@Document(collection = "chat_user") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ChatUser { + + @Id + private String id; + + @Indexed(unique = true) + @Field("user_id") + private Long userId; + + @Field("nickname") + private String nickname; + + @Field("profile_url") + private String profileUrl; + + @Builder + public ChatUser(Long userId, String nickname, String profileUrl) { + this.userId = userId; + this.nickname = nickname; + this.profileUrl = profileUrl; + } + + public void updateUserInfo(String nickname, String profileUrl) { + if (nickname != null) { + this.nickname = nickname; + } + if (profileUrl != null) { + this.profileUrl = profileUrl; + } + } +} diff --git a/src/main/java/com/example/daobe/chat/domain/repository/ChatUserRepository.java b/src/main/java/com/example/daobe/chat/domain/repository/ChatUserRepository.java new file mode 100644 index 00000000..7a2421a7 --- /dev/null +++ b/src/main/java/com/example/daobe/chat/domain/repository/ChatUserRepository.java @@ -0,0 +1,14 @@ +package com.example.daobe.chat.domain.repository; + +import com.example.daobe.chat.domain.ChatUser; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import org.springframework.data.mongodb.repository.MongoRepository; + +public interface ChatUserRepository extends MongoRepository { + + Optional findByUserId(Long userId); + + List findAllByUserIdIn(Set userIds); +} diff --git a/src/main/java/com/example/daobe/common/config/RedisConfig.java b/src/main/java/com/example/daobe/common/config/RedisConfig.java index e4609c29..50f74331 100644 --- a/src/main/java/com/example/daobe/common/config/RedisConfig.java +++ b/src/main/java/com/example/daobe/common/config/RedisConfig.java @@ -1,11 +1,9 @@ package com.example.daobe.common.config; import com.example.daobe.auth.domain.Token; -import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; @@ -13,26 +11,10 @@ @EnableRedisRepositories public class RedisConfig { - private final String host; - private final int port; - - public RedisConfig( - @Value("${spring.data.redis.host}") String host, - @Value("${spring.data.redis.port}") int port - ) { - this.host = host; - this.port = port; - } - - @Bean - public RedisConnectionFactory redisConnectionFactory() { - return new LettuceConnectionFactory(host, port); - } - @Bean - public RedisTemplate redisTemplate() { + public RedisTemplate redisTemplate(RedisConnectionFactory factory) { RedisTemplate redisTemplate = new RedisTemplate<>(); - redisTemplate.setConnectionFactory(redisConnectionFactory()); + redisTemplate.setConnectionFactory(factory); return redisTemplate; } } diff --git a/src/main/java/com/example/daobe/objet/application/ObjetService.java b/src/main/java/com/example/daobe/objet/application/ObjetService.java index a5059a60..acefa232 100644 --- a/src/main/java/com/example/daobe/objet/application/ObjetService.java +++ b/src/main/java/com/example/daobe/objet/application/ObjetService.java @@ -136,6 +136,7 @@ public ObjetDetailInfoDto getObjetDetail(Long objetId) { .name(findObjet.getName()) .userId(findObjet.getUser().getId()) .nickname(findObjet.getUser().getNickname()) + .loungeId(findObjet.getLounge().getId()) .objetImage(findObjet.getImageUrl()) .description(findObjet.getExplanation()) .type(findObjet.getType()) diff --git a/src/main/java/com/example/daobe/objet/application/dto/ObjetDetailInfoDto.java b/src/main/java/com/example/daobe/objet/application/dto/ObjetDetailInfoDto.java index 761c46b4..0a454ab4 100644 --- a/src/main/java/com/example/daobe/objet/application/dto/ObjetDetailInfoDto.java +++ b/src/main/java/com/example/daobe/objet/application/dto/ObjetDetailInfoDto.java @@ -8,6 +8,7 @@ public record ObjetDetailInfoDto( Long objetId, Long userId, + Long loungeId, String name, String nickname, String objetImage, diff --git a/src/main/java/com/example/daobe/user/application/UserService.java b/src/main/java/com/example/daobe/user/application/UserService.java index 0d32526e..1687b617 100644 --- a/src/main/java/com/example/daobe/user/application/UserService.java +++ b/src/main/java/com/example/daobe/user/application/UserService.java @@ -10,6 +10,7 @@ import com.example.daobe.user.domain.User; import com.example.daobe.user.domain.UserStatus; import com.example.daobe.user.domain.event.UserPokeEvent; +import com.example.daobe.user.domain.event.UserUpdateEvent; import com.example.daobe.user.domain.repository.UserPokeRepository; import com.example.daobe.user.domain.repository.UserRepository; import com.example.daobe.user.exception.UserException; @@ -49,13 +50,14 @@ public void checkValidateByNickname(String nickname) { } @Transactional - public UpdateProfileResponseDto updateNickname(Long userId, UpdateProfileRequestDto request) { + public UpdateProfileResponseDto updateProfile(Long userId, UpdateProfileRequestDto request) { User findUser = userRepository.findById(userId) .orElseThrow(() -> new UserException(NOT_EXIST_USER)); - findUser.updateUserInfo(request.nickname(), request.profileUrl()); userRepository.save(findUser); + eventPublisher.publishEvent(UserUpdateEvent.of(findUser)); + return UpdateProfileResponseDto.of(findUser); } @@ -77,7 +79,7 @@ public void poke(Long userId, UserPokeRequestDto request) { eventPublisher.publishEvent(userPokeEvent); } - // FIXME: External Service + // FIXME: EDA 기반으로 수정 public User getUserById(Long userId) { return userRepository.findById(userId) .orElseThrow(() -> new UserException(NOT_EXIST_USER)); diff --git a/src/main/java/com/example/daobe/user/domain/event/UserCreateEvent.java b/src/main/java/com/example/daobe/user/domain/event/UserCreateEvent.java new file mode 100644 index 00000000..6a610d86 --- /dev/null +++ b/src/main/java/com/example/daobe/user/domain/event/UserCreateEvent.java @@ -0,0 +1,18 @@ +package com.example.daobe.user.domain.event; + +import com.example.daobe.user.domain.User; + +public record UserCreateEvent( + Long userId, + String nickname, + String profileUrl +) { + + public static UserCreateEvent of(User user) { + return new UserCreateEvent( + user.getId(), + user.getNickname(), + user.getProfileUrl() + ); + } +} diff --git a/src/main/java/com/example/daobe/user/domain/event/UserUpdateEvent.java b/src/main/java/com/example/daobe/user/domain/event/UserUpdateEvent.java new file mode 100644 index 00000000..960db869 --- /dev/null +++ b/src/main/java/com/example/daobe/user/domain/event/UserUpdateEvent.java @@ -0,0 +1,18 @@ +package com.example.daobe.user.domain.event; + +import com.example.daobe.user.domain.User; + +public record UserUpdateEvent( + Long userId, + String nickname, + String profileUrl +) { + + public static UserUpdateEvent of(User user) { + return new UserUpdateEvent( + user.getId(), + user.getNickname(), + user.getProfileUrl() + ); + } +} diff --git a/src/main/java/com/example/daobe/user/presentation/UserController.java b/src/main/java/com/example/daobe/user/presentation/UserController.java index 2fbb6e69..65ac4ffd 100644 --- a/src/main/java/com/example/daobe/user/presentation/UserController.java +++ b/src/main/java/com/example/daobe/user/presentation/UserController.java @@ -74,7 +74,7 @@ public ResponseEntity> updateProfile( ) { return ResponseEntity.ok(new ApiResponse<>( "UPDATE_PROFILE_SUCCESS", - userService.updateNickname(userId, request) + userService.updateProfile(userId, request) )); } diff --git a/src/main/resources/config b/src/main/resources/config index aeb4980c..bc25832b 160000 --- a/src/main/resources/config +++ b/src/main/resources/config @@ -1 +1 @@ -Subproject commit aeb4980c881bd0179169e5dd82621dceca7375a4 +Subproject commit bc25832b1c435f6e6090d3dec0f472d52cce2f01