From a1b6b5781feacf55c5bfc6c796d774c047a6b666 Mon Sep 17 00:00:00 2001 From: Dom1046 Date: Thu, 2 Jan 2025 00:09:33 +0900 Subject: [PATCH 1/2] =?UTF-8?q?[refactor]=20redis=20publish=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/chat/redis/RedisPublisher.java | 22 ++++++++ .../domain/chat/redis/RedisSubscriber.java | 10 ++-- .../chat/service/ChatMessageService.java | 4 +- .../mallangs/global/config/RedisConfig.java | 56 +++++++++++++------ 4 files changed, 70 insertions(+), 22 deletions(-) create mode 100644 src/main/java/com/mallangs/domain/chat/redis/RedisPublisher.java diff --git a/src/main/java/com/mallangs/domain/chat/redis/RedisPublisher.java b/src/main/java/com/mallangs/domain/chat/redis/RedisPublisher.java new file mode 100644 index 00000000..22b2e240 --- /dev/null +++ b/src/main/java/com/mallangs/domain/chat/redis/RedisPublisher.java @@ -0,0 +1,22 @@ +package com.mallangs.domain.chat.redis; + +import com.mallangs.domain.chat.dto.response.ChatMessageResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +@Log4j2 +@Component +@RequiredArgsConstructor +public class RedisPublisher { + + private final RedisTemplate redisTemplate; + //레디스함 채널명 + private static final String CHAT_CHANNEL = "chat_channel"; + + //레디스함으로 메세지 송신 + public void publish(ChatMessageResponse message) { + redisTemplate.convertAndSend(CHAT_CHANNEL, message); + } +} diff --git a/src/main/java/com/mallangs/domain/chat/redis/RedisSubscriber.java b/src/main/java/com/mallangs/domain/chat/redis/RedisSubscriber.java index b8bdace5..d43dcc40 100644 --- a/src/main/java/com/mallangs/domain/chat/redis/RedisSubscriber.java +++ b/src/main/java/com/mallangs/domain/chat/redis/RedisSubscriber.java @@ -19,26 +19,26 @@ public class RedisSubscriber implements MessageListener { private final SimpMessageSendingOperations messagingTemplate; private final RedisTemplate redisTemplate; - //Stomp로 송신 + //stomp (레디스 거치지 않고) 바로 메세지 전송 public void sendMessage(ChatMessageResponse publishMessage) { try { - log.info("레디스 펍섭의 publishMessage: {}", publishMessage); messagingTemplate.convertAndSend("/sub/chat/room/" + publishMessage.getChatRoomId(), publishMessage); } catch (Exception e) { log.error("Exception {}", e.getMessage()); } } - //redis로 송수신 + //publish Redis 메세지 수신 @Override public void onMessage(Message message, byte[] pattern) { try { + //레디스 역직렬화 String publishMessage = (String) redisTemplate.getStringSerializer().deserialize(message.getBody()); - ChatMessageResponse chatMessage = objectMapper.readValue(publishMessage, ChatMessageResponse.class); + //stomp 이용해서 구독자들에게 메세지 송신 messagingTemplate.convertAndSend("/sub/chat/room/" + chatMessage.getChatRoomId(), chatMessage); } catch (Exception e) { - log.error(e.getMessage()); + log.error("publish 레디스 메세지 -> stomp 메세지로 송신 실패 :{}", e.getMessage()); } } } diff --git a/src/main/java/com/mallangs/domain/chat/service/ChatMessageService.java b/src/main/java/com/mallangs/domain/chat/service/ChatMessageService.java index 4c223aa2..742b44da 100644 --- a/src/main/java/com/mallangs/domain/chat/service/ChatMessageService.java +++ b/src/main/java/com/mallangs/domain/chat/service/ChatMessageService.java @@ -8,6 +8,7 @@ import com.mallangs.domain.chat.entity.ChatMessage; import com.mallangs.domain.chat.entity.ChatRoom; import com.mallangs.domain.chat.entity.ParticipatedRoom; +import com.mallangs.domain.chat.redis.RedisPublisher; import com.mallangs.domain.chat.redis.RedisSubscriber; import com.mallangs.domain.chat.repository.ChatMessageRepository; import com.mallangs.domain.chat.repository.ParticipatedRoomRepository; @@ -41,6 +42,7 @@ public class ChatMessageService { private final ChatMessageRepository chatMessageRepository; private final ParticipatedRoomRepository participatedRoomRepository; + private final RedisPublisher redisPublisher; private final RedisSubscriber redisSubscriber; private final MemberRepository memberRepository; @@ -79,7 +81,7 @@ public ChatMessageSuccessResponse sendMessage(ChatMessageRequest chatMessageRequ log.info("마지막 메세지 보낼 채팅 정보: {}", chatMessageResponse.toString()); //redis로 채팅 보내기 - redisSubscriber.sendMessage(chatMessageResponse); + redisPublisher.publish(chatMessageResponse); return new ChatMessageSuccessResponse(savedChatMessage.getSender().getUserId().getValue()); } catch (Exception e) { throw new MallangsCustomException(ErrorCode.FAILED_CREATE_CHAT_MESSAGE); diff --git a/src/main/java/com/mallangs/global/config/RedisConfig.java b/src/main/java/com/mallangs/global/config/RedisConfig.java index f1c21f98..0168bceb 100644 --- a/src/main/java/com/mallangs/global/config/RedisConfig.java +++ b/src/main/java/com/mallangs/global/config/RedisConfig.java @@ -1,11 +1,14 @@ package com.mallangs.global.config; +import com.fasterxml.jackson.databind.ObjectMapper; import com.mallangs.domain.chat.redis.RedisSubscriber; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +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.listener.ChannelTopic; import org.springframework.data.redis.listener.RedisMessageListenerContainer; @@ -18,45 +21,66 @@ @Log4j2 public class RedisConfig { + @Value("${spring.data.redis.port}") + private int redisPort; + + @Value("${spring.data.redis.host}") + private String redisHost; + @Bean - public ChannelTopic topicPattern() { + public RedisConnectionFactory redisConnectionFactory() { + return new LettuceConnectionFactory(redisHost, redisPort); + } - return new ChannelTopic("chatRoom"); + @Bean + //Redis 채널명 + public ChannelTopic topicPattern() { + return new ChannelTopic("chat_channel"); } //클라이언트로 부터 메세지 수신 @Bean + //Redis 메세지 구독을 담당 public RedisMessageListenerContainer redisMessageListener(RedisConnectionFactory connectionFactory - , MessageListenerAdapter listenerAdapter, ChannelTopic channelTopic) { + ,MessageListenerAdapter listenerAdapter) { RedisMessageListenerContainer container = new RedisMessageListenerContainer(); - container.setConnectionFactory(connectionFactory); - container.addMessageListener(listenerAdapter, channelTopic); + container.setConnectionFactory(connectionFactory); //연결될 레디스 서버 주소, 포트 설정(매핑) + container.addMessageListener(listenerAdapter, topicPattern()); //연결될 레디스 채널명, 리스너 설정(매핑) return container; } - //클라이언트로 부터 메세지 수신 + @Bean + /** + <메세지의 흐름> + * messageService -> redisMessageListener -> listenerAdapter -> onMessage + : onMessage 실제 메세지 처리 로직 실행 (Stomp 메세지 전송) + + * listenerAdapter : message 랩핑 + onMessage 향해 메세지 전달역할 + */ public MessageListenerAdapter listenerAdapter(RedisSubscriber subscriber) { - return new MessageListenerAdapter(subscriber, "sendMessage"); + return new MessageListenerAdapter(subscriber, "onMessage"); } - // RedisConnectionFactory은 spring redis api에서 자동으로 빈 생성 @Bean(name = "redisTemplate") - public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { + //Redis 직렬화 방식 GenericJackson2 변경 + public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory + ,ObjectMapper objectMapper) { RedisTemplate template = new RedisTemplate<>(); template.setConnectionFactory(connectionFactory); // GenericJackson2JsonRedisSerializer 설정 - GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(); + GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer(objectMapper); - // Key Serializer 설정 - StringRedisSerializer - template.setKeySerializer(new StringRedisSerializer()); - template.setHashKeySerializer(new StringRedisSerializer()); + // Key Serializer 설정 + StringRedisSerializer stringSerializer = new StringRedisSerializer(); + template.setKeySerializer(stringSerializer); + template.setHashKeySerializer(stringSerializer); // Value Serializer 설정 - template.setValueSerializer(jackson2JsonRedisSerializer); - template.setHashValueSerializer(jackson2JsonRedisSerializer); + template.setValueSerializer(serializer); + template.setHashValueSerializer(serializer); - template.afterPropertiesSet(); + template.afterPropertiesSet(); // 설정 완료 후 초기화 return template; } } From ccc076e04e7f1d147b2a777c3a827f3ce853c1a0 Mon Sep 17 00:00:00 2001 From: Dom1046 Date: Thu, 2 Jan 2025 17:39:20 +0900 Subject: [PATCH 2/2] =?UTF-8?q?[fix]=20AI=20=EB=A1=9C=EC=A7=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20token=20=EA=B2=80=EC=A6=9Dx?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + .../domain/ai/AIPromptController.java | 21 ++-------------- .../article/service/ArticleService.java | 24 ++++++++++++++++--- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/build.gradle b/build.gradle index d4ef58b9..e447c5f8 100644 --- a/build.gradle +++ b/build.gradle @@ -109,6 +109,7 @@ dependencies { //AI prompt // implementation 'org.springframework.ai:spring-ai-vertex-ai-gemini-spring-boot-starter:1.0.0-SNAPSHOT' implementation 'com.google.cloud:google-cloud-vertexai:1.15.0' + implementation 'org.springframework.ai:spring-ai-vertex-ai-gemini' //jackson 날짜변환 위한 의존성, json 의존성 implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' diff --git a/src/main/java/com/mallangs/domain/ai/AIPromptController.java b/src/main/java/com/mallangs/domain/ai/AIPromptController.java index cb16a45f..a0e29040 100644 --- a/src/main/java/com/mallangs/domain/ai/AIPromptController.java +++ b/src/main/java/com/mallangs/domain/ai/AIPromptController.java @@ -14,7 +14,6 @@ import com.mallangs.domain.article.service.ArticleService; import com.mallangs.domain.board.dto.response.SightingListResponse; import com.mallangs.domain.board.service.BoardService; -import com.mallangs.domain.member.entity.Member; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import lombok.RequiredArgsConstructor; @@ -22,7 +21,6 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @@ -59,25 +57,10 @@ public class AIPromptController { public ResponseEntity> getArticleByArticleIdByAI( @Parameter(description = "조회할 글타래 ID", required = true) @PathVariable Long articleId) { - String memberRole; - Long memberId; - - Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); - - if (principal instanceof Member member) { - memberRole = member.getMemberRole().name(); - memberId = member.getMemberId(); - } else { - memberRole = "ROLE_GUEST"; - memberId = -1L; - } - log.info("role: {} memberId: {}", memberRole, memberId); - //단전조회, 실종글타래 조회 - ArticleResponse articleResponse = articleService.getArticleById(articleId, memberRole, - memberId); + ArticleResponse articleResponse = articleService.getLostArticleById(articleId); - //질문 제작 + //질문제작 StringBuilder question = new StringBuilder(); // JSON 형식에 대한 예시 설명 diff --git a/src/main/java/com/mallangs/domain/article/service/ArticleService.java b/src/main/java/com/mallangs/domain/article/service/ArticleService.java index d87cdb52..54e79523 100644 --- a/src/main/java/com/mallangs/domain/article/service/ArticleService.java +++ b/src/main/java/com/mallangs/domain/article/service/ArticleService.java @@ -41,13 +41,13 @@ public ArticleResponse createArticle(ArticleCreateRequest articleCreateRequest, .orElseThrow(() -> new MallangsCustomException(ErrorCode.MEMBER_NOT_FOUND)); // 팩토리 매니저를 통해 적절한 팩토리 선택 -> 팩토리의 역할: - log.info("articleCreateRequest: {}",articleCreateRequest.toString()); - log.info("articleCreateRequest 설명: {}",articleCreateRequest.getArticleType().getDescription()); + log.info("articleCreateRequest: {}", articleCreateRequest.toString()); + log.info("articleCreateRequest 설명: {}", articleCreateRequest.getArticleType().getDescription()); ArticleFactory factory = factoryManager.getFactory( articleCreateRequest.getArticleType().getDescription()); // 팩토리에서 article 생성 - Article article = factory.createArticle(foundMember, articleCreateRequest); + Article article = factory.createArticle(foundMember, articleCreateRequest); article.hideInMap(); // published 상태 아니면 map hidden Article savedArticle = articleRepository.save(article); @@ -147,6 +147,24 @@ public ArticleResponse getArticleById(Long articleId, String userRole, Long memb throw new MallangsCustomException(ErrorCode.ARTICLE_NOT_FOUND); } + // 실종 글타래 단건 조회 (수정본) + // 사용자는 map visiblie 인 경우 + public ArticleResponse getLostArticleById(Long articleId) { + Article foundArticle = articleRepository.findById(articleId) + .orElseThrow(() -> new MallangsCustomException(ErrorCode.ARTICLE_NOT_FOUND)); + + try { + // ArticleResponse 로 반환 통일되게 팩토리메서드 작성 필요 + ArticleFactory factory = factoryManager.getFactory( + foundArticle.getArticleType().getDescription()); + log.info("factory. : {}", factory); + + return factory.createResponse(foundArticle); + } catch (Exception e) { + throw new MallangsCustomException(ErrorCode.ARTICLE_NOT_FOUND); + } + } + // 목격제보 글타래 전체조회 public List getSightArticleByLostId(Long LostArticleId) { List
articles = articleRepository.findSightingArticles(LostArticleId);