Skip to content

Commit

Permalink
Merge pull request #265 from prgrms-web-devcourse-final-project/feature/
Browse files Browse the repository at this point in the history
#234-AI-prompt-추가

AI 서비스 로직 수정, 채팅 Redis publisher 추가
  • Loading branch information
Dom1046 authored Jan 2, 2025
2 parents bf054ae + ccc076e commit 1d017fd
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 44 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
21 changes: 2 additions & 19 deletions src/main/java/com/mallangs/domain/ai/AIPromptController.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,13 @@
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;
import lombok.extern.log4j.Log4j2;
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;
Expand Down Expand Up @@ -59,25 +57,10 @@ public class AIPromptController {
public ResponseEntity<List<SightAIResponse>> 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 형식에 대한 예시 설명
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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<ArticleResponse> getSightArticleByLostId(Long LostArticleId) {
List<Article> articles = articleRepository.findSightingArticles(LostArticleId);
Expand Down
22 changes: 22 additions & 0 deletions src/main/java/com/mallangs/domain/chat/redis/RedisPublisher.java
Original file line number Diff line number Diff line change
@@ -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<String, Object> redisTemplate;
//레디스함 채널명
private static final String CHAT_CHANNEL = "chat_channel";

//레디스함으로 메세지 송신
public void publish(ChatMessageResponse message) {
redisTemplate.convertAndSend(CHAT_CHANNEL, message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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);
Expand Down
56 changes: 40 additions & 16 deletions src/main/java/com/mallangs/global/config/RedisConfig.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
//Redis 직렬화 방식 GenericJackson2 변경
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory
,ObjectMapper objectMapper) {
RedisTemplate<String, Object> 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;
}
}

0 comments on commit 1d017fd

Please sign in to comment.