diff --git a/build.gradle b/build.gradle index 7b486fc..c03793d 100644 --- a/build.gradle +++ b/build.gradle @@ -28,6 +28,12 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' +<<<<<<< HEAD +======= + // gpt + implementation 'io.github.flashvayne:chatgpt-spring-boot-starter:1.0.5' + +>>>>>>> b63641a0ca3b6bfba9938ee558bf8e5a7bf49013 // security implementation 'org.springframework.boot:spring-boot-starter-security' @@ -36,6 +42,10 @@ dependencies { runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' +<<<<<<< HEAD +======= + +>>>>>>> b63641a0ca3b6bfba9938ee558bf8e5a7bf49013 compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' diff --git a/src/main/java/com/hsu/shimpyoo/domain/breathing/service/BreathingCheckServiceImpl.java b/src/main/java/com/hsu/shimpyoo/domain/breathing/service/BreathingCheckServiceImpl.java index fc0a016..9b06743 100644 --- a/src/main/java/com/hsu/shimpyoo/domain/breathing/service/BreathingCheckServiceImpl.java +++ b/src/main/java/com/hsu/shimpyoo/domain/breathing/service/BreathingCheckServiceImpl.java @@ -152,7 +152,7 @@ public void deleteBreathing() { } // 플라스크 서버 엔드포인트 - String flaskUrl = "http://localhost:5001/analyze"; + String flaskUrl = "http://15.165.141.134:5001/analyze"; // Flask 서버로 POST 요청을 보내서 PEF 값을 받아옴 ResponseEntity response = restTemplate.postForEntity(flaskUrl, breathingFlaskRequestDto, Map.class); @@ -166,7 +166,7 @@ public void deleteBreathing() { .third(pefValues.get("pef_3")) .build(); - // 최대 수치 서렂ㅇ + // 최대 수치 설정 Double maxPef = Math.max(breathingPefDto.getFirst(), Math.max(breathingPefDto.getSecond(), breathingPefDto.getThird())); @@ -185,6 +185,11 @@ public void deleteBreathing() { throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "통신 중 서버 오류가 발생했습니다."); } + // 응답 본문이 비어 있는 경우 예외 처리 + if (response.getBody() == null) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "서버로부터 올바른 응답을 받지 못했습니다."); + } + return newBreathing; } diff --git a/src/main/java/com/hsu/shimpyoo/domain/breathing/service/BreathingService.java b/src/main/java/com/hsu/shimpyoo/domain/breathing/service/BreathingService.java index aafac07..7f98c1c 100644 --- a/src/main/java/com/hsu/shimpyoo/domain/breathing/service/BreathingService.java +++ b/src/main/java/com/hsu/shimpyoo/domain/breathing/service/BreathingService.java @@ -88,7 +88,8 @@ public CustomAPIResponse> calculateBreathingResult(Breathing Map responseData = new LinkedHashMap<>(); responseData.put("status", state.getDescription()); // 한국어 설명으로 반환 responseData.put("breathingRate", maxBreathingRate); - responseData.put("rateDifference", rateDifferencePercent + "% " + rateChangeDirection); + responseData.put("variance", rateDifferencePercent + "%"); // 증감률 + responseData.put("rateChangeDirection", rateChangeDirection); responseData.put("first", todayBreathing.getFirst()); responseData.put("second", todayBreathing.getSecond()); responseData.put("third", todayBreathing.getThird()); diff --git a/src/main/java/com/hsu/shimpyoo/domain/chatbot/entity/Chat.java b/src/main/java/com/hsu/shimpyoo/domain/chatbot/entity/Chat.java new file mode 100644 index 0000000..dbd14e0 --- /dev/null +++ b/src/main/java/com/hsu/shimpyoo/domain/chatbot/entity/Chat.java @@ -0,0 +1,37 @@ +package com.hsu.shimpyoo.domain.chatbot.entity; + +import com.hsu.shimpyoo.domain.user.entity.User; +import com.hsu.shimpyoo.global.entity.BaseEntity; +import com.hsu.shimpyoo.global.enums.TFStatus; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Table(name="CHAT") +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Chat extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "chat_id") + private Long chatId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User userId; // 사용자 기본키 + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name="chatting_room_id", nullable = false) + private ChatRoom chatRoomId; // 채팅방 기본키 + + @Enumerated(EnumType.STRING) + @Column(name="is_send") + private TFStatus isSend; // 수신 여부 (true -> 받은 메시지, false -> 보낸 메시지) + + @Column(name="content", columnDefinition = "TEXT") + private String content; // 메시지의 내용 + +} diff --git a/src/main/java/com/hsu/shimpyoo/domain/chatbot/entity/ChatRoom.java b/src/main/java/com/hsu/shimpyoo/domain/chatbot/entity/ChatRoom.java new file mode 100644 index 0000000..3cd252b --- /dev/null +++ b/src/main/java/com/hsu/shimpyoo/domain/chatbot/entity/ChatRoom.java @@ -0,0 +1,28 @@ +package com.hsu.shimpyoo.domain.chatbot.entity; + +import com.hsu.shimpyoo.domain.user.entity.User; +import com.hsu.shimpyoo.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Table(name="CHAT_ROOM") +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ChatRoom extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "chat_room_id") + private Long chatRoomId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User userId; // 사용자 기본키 + + @Column(name="chat_title") + private String chatTitle; // 채팅방 제목 + +} diff --git a/src/main/java/com/hsu/shimpyoo/domain/chatbot/repository/ChatRepository.java b/src/main/java/com/hsu/shimpyoo/domain/chatbot/repository/ChatRepository.java new file mode 100644 index 0000000..239fde8 --- /dev/null +++ b/src/main/java/com/hsu/shimpyoo/domain/chatbot/repository/ChatRepository.java @@ -0,0 +1,19 @@ +package com.hsu.shimpyoo.domain.chatbot.repository; + +import com.hsu.shimpyoo.domain.chatbot.entity.Chat; +import com.hsu.shimpyoo.domain.chatbot.entity.ChatRoom; +import com.hsu.shimpyoo.domain.user.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +@Repository +public interface ChatRepository extends JpaRepository { + // 사용자 기본키와 채팅방 기본키로 검색하여 가장 최근의 메시지 반환 + Optional findTopByUserIdAndChatRoomIdOrderByCreatedAtDesc(User user, ChatRoom chatRoom); + + // 채팅방에서 채팅 상세 조회 + List findChatByUserIdAndChatRoomId(User user, ChatRoom chatRoom); +} diff --git a/src/main/java/com/hsu/shimpyoo/domain/chatbot/repository/ChatRoomRepository.java b/src/main/java/com/hsu/shimpyoo/domain/chatbot/repository/ChatRoomRepository.java new file mode 100644 index 0000000..5c43355 --- /dev/null +++ b/src/main/java/com/hsu/shimpyoo/domain/chatbot/repository/ChatRoomRepository.java @@ -0,0 +1,21 @@ +package com.hsu.shimpyoo.domain.chatbot.repository; + +import com.hsu.shimpyoo.domain.chatbot.entity.ChatRoom; +import com.hsu.shimpyoo.domain.user.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +@Repository +public interface ChatRoomRepository extends JpaRepository { + // 사용자의 모든 채팅방 조회 + List findChatRoomByUserId(User user); + + // 채팅방 기본키로 채팅방 조회 + Optional findChatRoomByChatRoomId(Long id); + + // 채팅방 제목에 키워드가 포함된 채팅방 조회 + List findChatRoomByChatTitleContainingAndUserId(String keyword, User user); +} diff --git a/src/main/java/com/hsu/shimpyoo/domain/chatbot/service/ChatRoomService.java b/src/main/java/com/hsu/shimpyoo/domain/chatbot/service/ChatRoomService.java new file mode 100644 index 0000000..791951c --- /dev/null +++ b/src/main/java/com/hsu/shimpyoo/domain/chatbot/service/ChatRoomService.java @@ -0,0 +1,22 @@ +package com.hsu.shimpyoo.domain.chatbot.service; + +import com.hsu.shimpyoo.domain.chatbot.web.dto.ModifyChatRoomTitleDto; +import com.hsu.shimpyoo.global.response.CustomAPIResponse; +import org.springframework.http.ResponseEntity; + +public interface ChatRoomService { + // 채팅방 생성 + ResponseEntity> makeChatRoom(); + + // 채팅방 제목 수정 + ResponseEntity> modifyChatRoomTitle(ModifyChatRoomTitleDto requestDto); + + // 채팅방 목록 조회 + ResponseEntity> getAllChatRooms(); + + // 채팅방 대화 내역 조회 + ResponseEntity> getChat(Long chatRoomId); + + // 채팅방 제목으로 검색 + ResponseEntity> getChatRoomByKeyword(String keyword); +} diff --git a/src/main/java/com/hsu/shimpyoo/domain/chatbot/service/ChatRoomServiceImpl.java b/src/main/java/com/hsu/shimpyoo/domain/chatbot/service/ChatRoomServiceImpl.java new file mode 100644 index 0000000..644f4a1 --- /dev/null +++ b/src/main/java/com/hsu/shimpyoo/domain/chatbot/service/ChatRoomServiceImpl.java @@ -0,0 +1,213 @@ +package com.hsu.shimpyoo.domain.chatbot.service; + +import com.hsu.shimpyoo.domain.chatbot.entity.Chat; +import com.hsu.shimpyoo.domain.chatbot.entity.ChatRoom; +import com.hsu.shimpyoo.domain.chatbot.repository.ChatRepository; +import com.hsu.shimpyoo.domain.chatbot.repository.ChatRoomRepository; +import com.hsu.shimpyoo.domain.chatbot.web.dto.ChatListDto; +import com.hsu.shimpyoo.domain.chatbot.web.dto.ChatRoomListDto; +import com.hsu.shimpyoo.domain.chatbot.web.dto.ModifyChatRoomTitleDto; +import com.hsu.shimpyoo.domain.user.entity.User; +import com.hsu.shimpyoo.domain.user.repository.UserRepository; +import com.hsu.shimpyoo.global.response.CustomAPIResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; + +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class ChatRoomServiceImpl implements ChatRoomService { + private final ChatRepository chatRepository; + private final ChatRoomRepository chatRoomRepository; + private final UserRepository userRepository; + + // 채팅방 생성 + @Override + public ResponseEntity> makeChatRoom(){ + // 현재 인증된 사용자의 로그인 아이디를 가져옴 (getName은 loginId를 가져오는 것) + String loginId= SecurityContextHolder.getContext().getAuthentication().getName(); + + // 사용자 존재 여부 확인 + Optional isExistUser=userRepository.findByLoginId(loginId); + if(isExistUser.isEmpty()){ + throw new ResponseStatusException(HttpStatus.NOT_FOUND,"존재하지 않는 사용자입니다."); + } + + ChatRoom chatRoom = ChatRoom. + builder() + .chatTitle("채팅방") + .userId(isExistUser.get()) + .build(); + + chatRoomRepository.save(chatRoom); + + CustomAPIResponse res=CustomAPIResponse.createSuccess(200, null, "채팅방이 생성되었습니다."); + return ResponseEntity.status(HttpStatus.OK).body(res); + } + + // 채팅방 제목 수정 + @Override + public ResponseEntity> modifyChatRoomTitle(ModifyChatRoomTitleDto requestDto) { + // 현재 인증된 사용자의 로그인 아이디를 가져옴 (getName은 loginId를 가져오는 것) + String loginId= SecurityContextHolder.getContext().getAuthentication().getName(); + + // 사용자 존재 여부 확인 + Optional isExistUser=userRepository.findByLoginId(loginId); + if(isExistUser.isEmpty()){ + throw new ResponseStatusException(HttpStatus.NOT_FOUND,"존재하지 않는 사용자입니다."); + } + + Optional isExistChatRoom = chatRoomRepository.findById(requestDto.getChatRoomId()); + + if(isExistChatRoom.isEmpty()){ + throw new ResponseStatusException(HttpStatus.NOT_FOUND,"존재하지 않는 채팅방입니다."); + } + + // 채팅방이 현재 로그인한 사용자의 채팅방이 아니라면 + if(isExistChatRoom.get().getUserId()!=isExistUser.get()){ + throw new ResponseStatusException(HttpStatus.FORBIDDEN,"채팅방 제목 수정 권한이 없습니다."); + } + + isExistChatRoom.get().setChatTitle(requestDto.getTitle()); + chatRoomRepository.save(isExistChatRoom.get()); + + CustomAPIResponse res = CustomAPIResponse.createSuccess(200, null , + "채팅방 제목이 수정되었습니다."); + return ResponseEntity.status(HttpStatus.OK).body(res); + } + + // 모든 채팅방 목록 조회 + @Override + public ResponseEntity> getAllChatRooms() { + // 현재 인증된 사용자의 로그인 아이디를 가져옴 (getName은 loginId를 가져오는 것) + String loginId= SecurityContextHolder.getContext().getAuthentication().getName(); + + // 사용자 존재 여부 확인 + Optional isExistUser=userRepository.findByLoginId(loginId); + if(isExistUser.isEmpty()){ + throw new ResponseStatusException(HttpStatus.NOT_FOUND,"존재하지 않는 사용자입니다."); + } + + // 사용자 아이디로 채팅방 검색 + List chatRoomList= chatRoomRepository.findChatRoomByUserId(isExistUser.get()); + + // 채팅방 정보 목록을 담을 리스트 생성 + List chatRoomListDtos = new ArrayList<>(); + + // 각 채팅방에 대해 마지막 메시지 조회 + for (ChatRoom chatRoom : chatRoomList) { + // 마지막 메시지를 ChatRepository를 통해 조회 + Optional lastChat = chatRepository.findTopByUserIdAndChatRoomIdOrderByCreatedAtDesc(isExistUser.get(), chatRoom); + + // dto의 마지막 메시지와 마지막 시간 정보 추가 + String lastChatMessage = lastChat.map(Chat::getContent).orElse("메시지가 존재하지 않습니다."); + String lastChatAt = lastChat.map(chat -> chat.getCreatedAt().format(DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm"))) + .orElse("시간이 존재하지 않습니다."); + + // ChatRoomListDto에 값을 채워서 리스트에 추가 + ChatRoomListDto chatRoomListDto = new ChatRoomListDto( + chatRoom.getChatRoomId(), // 채팅방 ID + chatRoom.getChatTitle(), // 채팅방 제목 + lastChatMessage, // 마지막 메시지 + lastChatAt // 마지막 대화 시간 + ); + + chatRoomListDtos.add(chatRoomListDto); + } + + CustomAPIResponse> response = CustomAPIResponse.createSuccess(200, chatRoomListDtos, "채팅방 목록 조회에 성공하였습니다."); + return ResponseEntity.ok(response); + + } + + @Override + public ResponseEntity> getChat(Long chatRoomId) { + // 현재 인증된 사용자의 로그인 아이디를 가져옴 (getName은 loginId를 가져오는 것) + String loginId= SecurityContextHolder.getContext().getAuthentication().getName(); + + // 사용자 존재 여부 확인 + Optional isExistUser=userRepository.findByLoginId(loginId); + if(isExistUser.isEmpty()){ + throw new ResponseStatusException(HttpStatus.NOT_FOUND,"존재하지 않는 사용자입니다."); + } + + Optional isExistChatRoom = chatRoomRepository.findById(chatRoomId); + + if(isExistChatRoom.isEmpty()){ + throw new ResponseStatusException(HttpStatus.NOT_FOUND,"존재하지 않는 채팅방입니다."); + } + + // 채팅방이 현재 로그인한 사용자의 채팅방이 아니라면 + if(isExistChatRoom.get().getUserId()!=isExistUser.get()){ + throw new ResponseStatusException(HttpStatus.FORBIDDEN,"채팅방 조회 권한이 없습니다."); + } + + List chatList=chatRepository.findChatByUserIdAndChatRoomId(isExistUser.get(),isExistChatRoom.get()); + + // 대화 내역 dto를 담을 배열 생성 + List chatListDtos = new ArrayList<>(); + + for (Chat chat : chatList) { + ChatListDto chatListDto = new ChatListDto( + chat.getContent(), + chat.getIsSend() + ); + chatListDtos.add(chatListDto); + } + + CustomAPIResponse> response = CustomAPIResponse.createSuccess(200, chatListDtos, "대화 내역 조회에 성공하였습니다."); + return ResponseEntity.ok(response); + } + + + // 채팅방 검색 기능 + @Override + public ResponseEntity> getChatRoomByKeyword(String keyword) { + // 현재 인증된 사용자의 로그인 아이디를 가져옴 (getName은 loginId를 가져오는 것) + String loginId= SecurityContextHolder.getContext().getAuthentication().getName(); + + // 사용자 존재 여부 확인 + Optional isExistUser=userRepository.findByLoginId(loginId); + if(isExistUser.isEmpty()){ + throw new ResponseStatusException(HttpStatus.NOT_FOUND,"존재하지 않는 사용자입니다."); + } + + // 사용자 아이디와 키워드로 채팅방 검색 + List chatRoomList= chatRoomRepository.findChatRoomByChatTitleContainingAndUserId(keyword, isExistUser.get()); + + // 채팅방 정보 목록을 담을 리스트 생성 + List chatRoomListDtos = new ArrayList<>(); + + // 각 채팅방에 대해 마지막 메시지 조회 + for (ChatRoom chatRoom : chatRoomList) { + // 마지막 메시지를 ChatRepository를 통해 조회 + Optional lastChat = chatRepository.findTopByUserIdAndChatRoomIdOrderByCreatedAtDesc(isExistUser.get(), chatRoom); + + // dto의 마지막 메시지와 마지막 시간 정보 추가 + String lastChatMessage = lastChat.map(Chat::getContent).orElse("메시지가 존재하지 않습니다."); + String lastChatAt = lastChat.map(chat -> chat.getCreatedAt().format(DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm"))) + .orElse("시간이 존재하지 않습니다."); + + // ChatRoomListDto에 값을 채워서 리스트에 추가 + ChatRoomListDto chatRoomListDto = new ChatRoomListDto( + chatRoom.getChatRoomId(), // 채팅방 ID + chatRoom.getChatTitle(), // 채팅방 제목 + lastChatMessage, // 마지막 메시지 + lastChatAt // 마지막 대화 시간 + ); + + chatRoomListDtos.add(chatRoomListDto); + } + + CustomAPIResponse> response = CustomAPIResponse.createSuccess(200, chatRoomListDtos, "채팅방 목록 조회에 성공하였습니다."); + return ResponseEntity.ok(response); + } +} diff --git a/src/main/java/com/hsu/shimpyoo/domain/chatbot/service/ChatService.java b/src/main/java/com/hsu/shimpyoo/domain/chatbot/service/ChatService.java new file mode 100644 index 0000000..833f199 --- /dev/null +++ b/src/main/java/com/hsu/shimpyoo/domain/chatbot/service/ChatService.java @@ -0,0 +1,11 @@ +package com.hsu.shimpyoo.domain.chatbot.service; + +import com.hsu.shimpyoo.domain.chatbot.web.dto.ChatQuestionDto; +import com.hsu.shimpyoo.global.response.CustomAPIResponse; +import org.springframework.http.ResponseEntity; + +public interface ChatService { + ResponseEntity> askForChat(ChatQuestionDto chatQuestionDto); + +} + diff --git a/src/main/java/com/hsu/shimpyoo/domain/chatbot/service/ChatServiceImpl.java b/src/main/java/com/hsu/shimpyoo/domain/chatbot/service/ChatServiceImpl.java new file mode 100644 index 0000000..29dbe65 --- /dev/null +++ b/src/main/java/com/hsu/shimpyoo/domain/chatbot/service/ChatServiceImpl.java @@ -0,0 +1,128 @@ +package com.hsu.shimpyoo.domain.chatbot.service; + +import com.fasterxml.jackson.databind.JsonNode; +import com.hsu.shimpyoo.domain.chatbot.entity.Chat; +import com.hsu.shimpyoo.domain.chatbot.entity.ChatRoom; +import com.hsu.shimpyoo.domain.chatbot.repository.ChatRepository; +import com.hsu.shimpyoo.domain.chatbot.repository.ChatRoomRepository; +import com.hsu.shimpyoo.domain.chatbot.web.dto.ChatQuestionDto; +import com.hsu.shimpyoo.domain.chatbot.web.dto.ChatRequestDto; +import com.hsu.shimpyoo.domain.user.entity.User; +import com.hsu.shimpyoo.domain.user.repository.UserRepository; +import com.hsu.shimpyoo.global.enums.TFStatus; +import com.hsu.shimpyoo.global.response.CustomAPIResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.*; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.web.server.ResponseStatusException; + +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class ChatServiceImpl implements ChatService { + private final ChatRepository chatRepository; + private final UserRepository userRepository; + private final ChatRoomRepository chatRoomRepository; + + private final String apiUrl = "https://api.openai.com/v1/chat/completions"; + + @Value("${gpt.api.key}") + private String apiKey; + + @Override + public ResponseEntity> askForChat(ChatQuestionDto chatQuestionDto) { + String question= chatQuestionDto.getQuestion(); + Long chatRoomId=chatQuestionDto.getChatRoomId(); + + // 현재 인증된 사용자의 로그인 아이디를 가져옴 (getName은 loginId를 가져오는 것) + String loginId= SecurityContextHolder.getContext().getAuthentication().getName(); + + // 사용자 존재 여부 확인 + Optional isExistUser = userRepository.findByLoginId(loginId); + if (isExistUser.isEmpty()) { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(CustomAPIResponse.createFailWithout(404, "존재하지 않는 사용자입니다.")); + } + + Optional isExistChatRoom = chatRoomRepository.findChatRoomByChatRoomId(chatRoomId); + // 채팅방이 존재하지 않는다면 + if (isExistChatRoom.isEmpty()) { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(CustomAPIResponse.createFailWithout(404, "존재하지 않는 채팅방입니다.")); + } + + // 채팅방이 사용자의 채팅방이 아니라면 + if (!isExistChatRoom.get().getUserId().equals(isExistUser.get())) { + return ResponseEntity.status(HttpStatus.FORBIDDEN) + .body(CustomAPIResponse.createFailWithout(403, "접근할 수 없는 채팅방입니다.")); + } + + try { + String model = "gpt-3.5-turbo"; // 모델 명 + String role = "user"; // 역할 + + // 요청 메시지 구성 + String prompt = "당신은 지상 최고의 천식 전문가입니다. 최선을 다 해서 천식에 관한 답을 해주세요." + + "답변은 100자 이내로 부탁드립니다. 천식과 관련된 질문은 반드시 답해야만 합니다." + + "천식에 대한 질문이 아니라서 답변을 할 수 없는 경우에만 '저는 천식 관련 챗봇이에요, 천식과 관련된 질문만 답변드릴 수 있습니다.'" + + "는 답변을 해주길 바랍니다."+"'네, 알겠습니다'와 같은 응답은 불필요합니다."+"질문은 다음과 같습니다."; + + ChatRequestDto requestDto = new ChatRequestDto(model, prompt+question); + + // HTTP 헤더 설정 + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setBearerAuth(apiKey); + + // 요청 바디를 JSON으로 직렬화 + ObjectMapper objectMapper = new ObjectMapper(); + String requestBody = objectMapper.writeValueAsString(requestDto); + + // HTTP 엔티티 생성 + HttpEntity entity = new HttpEntity<>(requestBody, headers); + + // RestTemplate을 통한 API 호출 + RestTemplate restTemplate = new RestTemplate(); + ResponseEntity response = restTemplate.exchange(apiUrl, HttpMethod.POST, entity, String.class); + // 응답 처리 + if (response.getStatusCode() == HttpStatus.OK) { + // 응답 본문에서 content만 추출 + + JsonNode jsonResponse = objectMapper.readTree(response.getBody()); + String content = jsonResponse.path("choices").get(0).path("message").path("content").asText(); + + // 보낸 메시지를 저장 + Chat sendChat =Chat.builder() + .userId(isExistUser.get()) + .isSend(TFStatus.TRUE) + .content(question) + .chatRoomId(isExistChatRoom.get()) + .build(); + chatRepository.save(sendChat); + + // 받은 메시지를 저장 + Chat receivedChat=Chat.builder() + .userId(isExistUser.get()) + .isSend(TFStatus.FALSE) + .content(content) + .chatRoomId(isExistChatRoom.get()) + .build(); + chatRepository.save(receivedChat); + + CustomAPIResponse res=CustomAPIResponse.createSuccess(200, content, "성공적으로 답변이 제공되었습니다."); + return ResponseEntity.status(HttpStatus.OK).body(res); + } else { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR,"API 호출 실패 : " + response.getStatusCode()); + } + + } catch (Exception e) { + e.printStackTrace(); + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR,"API 호출 중 오류 발생 : " + e.getMessage()); + } + } +} diff --git a/src/main/java/com/hsu/shimpyoo/domain/chatbot/web/controller/ChatController.java b/src/main/java/com/hsu/shimpyoo/domain/chatbot/web/controller/ChatController.java new file mode 100644 index 0000000..a202d93 --- /dev/null +++ b/src/main/java/com/hsu/shimpyoo/domain/chatbot/web/controller/ChatController.java @@ -0,0 +1,73 @@ +package com.hsu.shimpyoo.domain.chatbot.web.controller; + +import com.hsu.shimpyoo.domain.chatbot.service.ChatRoomService; +import com.hsu.shimpyoo.domain.chatbot.web.dto.ChatQuestionDto; +import com.hsu.shimpyoo.domain.chatbot.service.ChatService; +import com.hsu.shimpyoo.domain.chatbot.web.dto.ModifyChatRoomTitleDto; +import com.hsu.shimpyoo.global.response.CustomAPIResponse; +import jakarta.validation.Valid; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import lombok.RequiredArgsConstructor; +import org.springframework.web.server.ResponseStatusException; + +@RestController +@RequestMapping("/api/chat") +@RequiredArgsConstructor +public class ChatController { + + private final ChatService chatService; + private final ChatRoomService chatRoomService; + + // 채팅방 생성 + @PostMapping("/makeChatRoom") + public ResponseEntity> makeChatRoom(){ + ResponseEntity> response =chatRoomService.makeChatRoom(); + return response; + } + + // 채팅방 제목 수정 + @PutMapping("/modifyChatRoomTitle") + public ResponseEntity> modifyChatRoomTitle(@RequestBody @Valid ModifyChatRoomTitleDto modifyChatRoomTitleDto){ + ResponseEntity> response=chatRoomService.modifyChatRoomTitle(modifyChatRoomTitleDto); + return response; + } + + // 사용자 입력 메시지를 받아서 처리 + @PostMapping("/ask") + public ResponseEntity> askChat(@RequestBody @Valid ChatQuestionDto chatQuestionDto) { + try { + // ChatService를 호출하여 메시지 처리 + ResponseEntity> response = chatService.askForChat(chatQuestionDto); + + // 성공 시 응답 반환 + return response; + } catch (Exception e) { + // 오류 발생 시 500 Internal Server Error와 함께 메시지 반환 + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR,"API 호출 중 오류가 발생했습니다."); + } + } + + // 채팅방 목록 조회 + @GetMapping("/getAllChatRoom") + public ResponseEntity> getAllChatRoom(){ + ResponseEntity> response=chatRoomService.getAllChatRooms(); + return response; + } + + // 채팅방 대화 내역 상세 조회 + @GetMapping("/getChat") + public ResponseEntity> getChat(@RequestParam Long chatRoomId){ + ResponseEntity> response=chatRoomService.getChat(chatRoomId); + return response; + } + + // 키워드로 채팅방 검색 + @GetMapping("/getChatRoomByKeyword") + public ResponseEntity> getChatRoomByKeyword(@RequestParam String keyword){ + ResponseEntity> response=chatRoomService.getChatRoomByKeyword(keyword); + return response; + } +} + diff --git a/src/main/java/com/hsu/shimpyoo/domain/chatbot/web/dto/ChatListDto.java b/src/main/java/com/hsu/shimpyoo/domain/chatbot/web/dto/ChatListDto.java new file mode 100644 index 0000000..fcc679b --- /dev/null +++ b/src/main/java/com/hsu/shimpyoo/domain/chatbot/web/dto/ChatListDto.java @@ -0,0 +1,19 @@ +package com.hsu.shimpyoo.domain.chatbot.web.dto; + +import com.hsu.shimpyoo.global.enums.TFStatus; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class ChatListDto { + // 내용 + private String content; + + // 수신 및 발신 여부 + private TFStatus isSend; +} diff --git a/src/main/java/com/hsu/shimpyoo/domain/chatbot/web/dto/ChatMessageDto.java b/src/main/java/com/hsu/shimpyoo/domain/chatbot/web/dto/ChatMessageDto.java new file mode 100644 index 0000000..b0c6b9f --- /dev/null +++ b/src/main/java/com/hsu/shimpyoo/domain/chatbot/web/dto/ChatMessageDto.java @@ -0,0 +1,15 @@ +package com.hsu.shimpyoo.domain.chatbot.web.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class ChatMessageDto { // OpenAI API와의 통신에서 메시지의 역할과 내용을 담는 dto + private String role; + private String content; +} diff --git a/src/main/java/com/hsu/shimpyoo/domain/chatbot/web/dto/ChatQuestionDto.java b/src/main/java/com/hsu/shimpyoo/domain/chatbot/web/dto/ChatQuestionDto.java new file mode 100644 index 0000000..b51036f --- /dev/null +++ b/src/main/java/com/hsu/shimpyoo/domain/chatbot/web/dto/ChatQuestionDto.java @@ -0,0 +1,17 @@ +package com.hsu.shimpyoo.domain.chatbot.web.dto; + +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class ChatQuestionDto { + // 채팅방 기본키 + @NotNull(message = "채팅방 기본키를 입력해주세요") + private Long chatRoomId; + + // 채팅방 질문 + @NotNull(message = "질문을 입력해주세요") + private String question; +} diff --git a/src/main/java/com/hsu/shimpyoo/domain/chatbot/web/dto/ChatRequestDto.java b/src/main/java/com/hsu/shimpyoo/domain/chatbot/web/dto/ChatRequestDto.java new file mode 100644 index 0000000..83efb58 --- /dev/null +++ b/src/main/java/com/hsu/shimpyoo/domain/chatbot/web/dto/ChatRequestDto.java @@ -0,0 +1,38 @@ +package com.hsu.shimpyoo.domain.chatbot.web.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.ArrayList; +import java.util.List; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ChatRequestDto { + + @JsonProperty("model") + private String model; + + @JsonProperty("messages") + private List messages; + + // 사용자 메시지만을 받아서 자동으로 model과 messages를 구성하는 생성자 + public ChatRequestDto(String model, String realMessage) { + this.model = model; + this.messages = new ArrayList<>(); + this.messages.add(new Message("user", realMessage)); + } + + public static class Message { + @JsonProperty("role") + private String role; + + @JsonProperty("content") + private String content; + + public Message(String role, String content) { + this.role = role; + this.content = content; + } + } + +} diff --git a/src/main/java/com/hsu/shimpyoo/domain/chatbot/web/dto/ChatResponseDto.java b/src/main/java/com/hsu/shimpyoo/domain/chatbot/web/dto/ChatResponseDto.java new file mode 100644 index 0000000..3b69c99 --- /dev/null +++ b/src/main/java/com/hsu/shimpyoo/domain/chatbot/web/dto/ChatResponseDto.java @@ -0,0 +1,23 @@ +package com.hsu.shimpyoo.domain.chatbot.web.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class ChatResponseDto { + private List choices; + + @Getter + @Setter + public static class ChatChoice { + private int index; + private ChatMessageDto message; + } +} \ No newline at end of file diff --git a/src/main/java/com/hsu/shimpyoo/domain/chatbot/web/dto/ChatRoomListDto.java b/src/main/java/com/hsu/shimpyoo/domain/chatbot/web/dto/ChatRoomListDto.java new file mode 100644 index 0000000..6855b7a --- /dev/null +++ b/src/main/java/com/hsu/shimpyoo/domain/chatbot/web/dto/ChatRoomListDto.java @@ -0,0 +1,27 @@ +package com.hsu.shimpyoo.domain.chatbot.web.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class ChatRoomListDto { + // 채팅방 기본키 + private Long chatRoomId; + + // 채팅방 제목 + private String chatRoomTitle; + + // 마지막으로 온 답변 + private String lastChat; + + // 마지막 대화 시간 + private String lastChatAt; + + +} diff --git a/src/main/java/com/hsu/shimpyoo/domain/chatbot/web/dto/ModifyChatRoomTitleDto.java b/src/main/java/com/hsu/shimpyoo/domain/chatbot/web/dto/ModifyChatRoomTitleDto.java new file mode 100644 index 0000000..ee123f9 --- /dev/null +++ b/src/main/java/com/hsu/shimpyoo/domain/chatbot/web/dto/ModifyChatRoomTitleDto.java @@ -0,0 +1,17 @@ +package com.hsu.shimpyoo.domain.chatbot.web.dto; + +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class ModifyChatRoomTitleDto { + // 새로운 제목 + @NotNull(message = "채팅방 제목을 입력해주세요") + private String title; + + // 채팅방 기본키 + @NotNull(message = "채팅방 기본키를 입력해주세요") + private Long chatRoomId; +} diff --git a/src/main/java/com/hsu/shimpyoo/global/enums/TFStatus.java b/src/main/java/com/hsu/shimpyoo/global/enums/TFStatus.java new file mode 100644 index 0000000..bb28cdf --- /dev/null +++ b/src/main/java/com/hsu/shimpyoo/global/enums/TFStatus.java @@ -0,0 +1,6 @@ +package com.hsu.shimpyoo.global.enums; + +public enum TFStatus { + TRUE, + FALSE +} diff --git a/src/main/java/com/hsu/shimpyoo/global/gpt/OpenAiConfig.java b/src/main/java/com/hsu/shimpyoo/global/gpt/OpenAiConfig.java new file mode 100644 index 0000000..0efe82c --- /dev/null +++ b/src/main/java/com/hsu/shimpyoo/global/gpt/OpenAiConfig.java @@ -0,0 +1,25 @@ +package com.hsu.shimpyoo.global.gpt; + +import jakarta.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + + +@Configuration +public class OpenAiConfig { + @Value("${gpt.api.key}") + private String apiKey; + + @Autowired + private RestTemplate restTemplate; + + @PostConstruct + public void addInterceptor() { + restTemplate.getInterceptors().add((request, body, execution) -> { + request.getHeaders().add("Authorization", "Bearer " + apiKey); + return execution.execute(request, body); + }); + } +}