Skip to content

Commit

Permalink
Merge pull request #43 from Move-Log/develop
Browse files Browse the repository at this point in the history
Merge develop to main
  • Loading branch information
EunbeenDev authored Jan 13, 2025
2 parents 8727901 + 5f5da0b commit faebc5a
Show file tree
Hide file tree
Showing 18 changed files with 405 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public ResponseEntity<AuthRes> login(@RequestBody IdTokenReq idTokenReq) {
authService.findOrCreateUser(idTokenReq.provider(), idTokenReq.idToken());
String email = authService.findEmail(providerId);

String accessToken = jwtUtil.createJwt("access", providerId, "ROLE_USER", 3600000L, email);
String accessToken = jwtUtil.createJwt("access", providerId, "ROLE_USER", 2592000000L, email);
System.out.println("accessToken = " + accessToken);
User user = userRepository.findByProviderId(providerId)
.orElseThrow(EntityNotFoundException::new);
Expand Down
63 changes: 63 additions & 0 deletions src/main/java/com/movelog/domain/gpt/application/GptService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.movelog.domain.gpt.application;

import com.fasterxml.jackson.databind.JsonNode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
@RequiredArgsConstructor
@Slf4j
public class GptService {

private final WebClient gptWebClient; // @Qualifier 제거하고 빈 이름에 맞는 생성자 주입
@Value("${chatgpt.api-key}")
private String apiKey;
@Value("${chatgpt.model}")
private String model;
@Value("${chatgpt.max-tokens}")
private Integer maxTokens;
@Value("${chatgpt.temperature}")
private Double temperature;
@Value("${chatgpt.top-p}")
private Double topP;

public Mono<JsonNode> callChatGpt(String prompt) {
return gptWebClient.post()
.uri("/chat/completions") // 경로에 대한 수정
.header("Authorization", "Bearer " + apiKey)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(buildRequestBody(prompt))
.retrieve()
.bodyToMono(JsonNode.class)
.doOnSuccess(response -> log.info("GPT API 응답 성공: {}", response))
.doOnError(error -> log.error("GPT API 호출 중 오류 발생: {}", error.getMessage()))
.onErrorResume(error -> {
log.error("GPT API 호출 중 예외 처리 발생: {}", error.getMessage());
return Mono.error(new RuntimeException("GPT API 호출 실패", error));
});
}

private Map<String, Object> buildRequestBody(String prompt) {
Map<String, Object> bodyMap = new HashMap<>();
bodyMap.put("model", model);
bodyMap.put("max_tokens", maxTokens);
bodyMap.put("temperature", temperature);
bodyMap.put("top_p", topP);
bodyMap.put("messages", List.of(
Map.of(
"role", "user",
"content", prompt
)
));
return bodyMap;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package com.movelog.domain.news.application;

import com.fasterxml.jackson.databind.JsonNode;
import com.movelog.domain.gpt.application.GptService;
import com.movelog.domain.news.dto.response.HeadLineRes;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.List;

@Service
@RequiredArgsConstructor
@Slf4j
public class HeadlineGeneratorService {

private final GptService gptService;

public List<HeadLineRes> generateHeadLine(String option, String verb, String noun) {
// 프롬프트 생성
String prompt = generatePrompt(option, verb, noun);

// 비동기 방식으로 GPT API 호출
return gptService.callChatGpt(prompt)
.flatMap(response -> {
if (response == null) {
return Mono.error(new RuntimeException("GPT API 응답이 없습니다."));
}

// 응답 파싱하여 List<CreatePracticeRes> 반환
List<HeadLineRes> practiceResList = parseResponse(response);
return Mono.just(practiceResList);
})
.doOnError(error -> log.error("문제 생성 중 오류 발생: ", error)).block();
}


// 프롬프트 생성
private String generatePrompt(String option, String verb, String noun) {
return String.format(
"다음 내용을 바탕으로 뉴스 헤드라인을 생성해주세요: %s\n" +
"당신은 뉴스 헤드라인을 작성하는 역할이며, 다음 형식을 엄격히 따라 뉴스 헤드라인을 제공해 주세요:\n\n" +
"이 뉴스 헤드라인의 목적은 사용자의 기록을 기반으로 약간의 재미 요소가 들어간 헤드라인을 제공하는 것입니다.\n" +
"각 뉴스 헤드라인은 반드시 격식있는 어체로 구성되어야 하며, 반드시 올바른 어법을 준수해야 합니다.\n" +
"각 헤드라인의 내용은 주어진 옵션, 동사, 명사를 기반으로 생성되어야 하며, 헤드라인 내용에 직접적인 내용이 들어가지 않아도 됩니다." +
"뉴스 헤드라인은 반드시 3개의 후보를 제공해야 하며, 각 후보는 1개의 줄바꿈으로 구분됩니다.\n" +
"각 헤드라인 내용의 구조는 반드시 쉼표로 나뉘어야 하며, 쉼표 이전의 내용에는 헤드라인 강조 옵션에 대한 내용이 포함되어야 합니다." +
"쉼표 앞 뒤의 내용에 대한 글자 수는 각각 16자 이하여야 하며, 쉼표는 반드시 한글자로만 구성되어야 합니다.\n\n" +

"다음은 제공된 정보를 통해 출력해야 하는 뉴스 헤드라인의 출력 예시입니다:\n\n" +
"꾸준한 노력 끝에 완성한 마라톤 기록, 도전의 의미는?\n오랜만의 첫 도전, 무엇이 그를 움직이게 했나?\n드디어 끊어낸 술, 운동으로 극복해내" +

"다음은 제공된 정보를 통해 출력해야 하는 뉴스 헤드라인의 출력 형식입니다:\n\n" +
"첫번째 헤드라인 후보 내용\n두번째 헤드라인 후보 내용\n세번째 헤드라인 후보 내용\n" +

"반드시 뉴스 헤드라인 형식을 준수해 주세요. 정해진 형식을 따르지 않으면 응답을 처리할 수 없습니다." +
"만약 결과 헤드라인 내용이 위 형식과 다르다면, 다시 요청하여 주세요.",
option, verb, noun
);
}

// GPT 응답 파싱
private List<HeadLineRes> parseResponse(JsonNode response) {
List<HeadLineRes> headLineResList = new ArrayList<>();

// GPT 응답에서 질문, 답변, 해설 추출
String textResponse = response.path("choices").get(0).path("message").path("content").asText().trim();
log.info("Full Text Response: " + textResponse); // 전체 응답 확인

String[] headLines = textResponse.split("\n");
// 헤드라인 생성 결과가 3개가 아닌 경우 예외 처리
if (headLines.length != 3) {
throw new RuntimeException("생성된 헤드라인이 3개가 아닙니다.");
}

// 헤드라인 후보 파싱 결과를 리스트에 저장 후 응답 리턴
for (String headLine : headLines) {
headLineResList.add(HeadLineRes.builder().headLine(headLine).build());
}

return headLineResList;
}
}
43 changes: 43 additions & 0 deletions src/main/java/com/movelog/domain/news/application/NewsService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.movelog.domain.news.application;

import com.movelog.domain.news.dto.request.NewsHeadLineReq;
import com.movelog.domain.news.dto.response.HeadLineRes;
import com.movelog.domain.user.application.UserService;
import com.movelog.domain.user.domain.User;
import com.movelog.domain.user.domain.repository.UserRepository;
import com.movelog.domain.user.exception.UserNotFoundException;
import com.movelog.global.DefaultAssert;
import com.movelog.global.config.security.token.UserPrincipal;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class NewsService {
private final HeadlineGeneratorService headlineGeneratorService;
private final UserService userService;
private final UserRepository userRepository;

public List<HeadLineRes> createHeadLine(UserPrincipal userPrincipal, NewsHeadLineReq newsHeadLineReq) {
User user = validateUser(userPrincipal);
// id가 5인 유저 정보(테스트용)
// User user = userRepository.findById(5L).orElseThrow(UserNotFoundException::new);
String option = newsHeadLineReq.getOption();
String verb = newsHeadLineReq.getVerb();
String noun = newsHeadLineReq.getNoun();
return headlineGeneratorService.generateHeadLine(option, verb, noun);
}


private User validateUser(UserPrincipal userPrincipal) {
Optional<User> userOptional = userService.findById(userPrincipal.getId());
DefaultAssert.isOptionalPresent(userOptional);
return userOptional.get();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.movelog.domain.news.dto.request;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
public class NewsHeadLineReq {
@Schema( type = "String", example ="첫 도전, 오랜만에 다시, 꾸준히 이어온 기록, 끊어낸 습관 중 택 1", description="뉴스 헤드라인 고정 옵션입니다.")
private String option;

@Schema( type = "String", example ="했어요, 먹었어요, 갔어요 중 택 1", description="사용자가 선택한 동사 정보입니다.")
private String verb;

@Schema( type = "String", example ="클라이밍", description="사용자가 선택한 명사 정보입니다.")
private String noun;


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.movelog.domain.news.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
public class HeadLineRes {
@Schema( type = "String", example ="5년 만의 첫 도전, 무엇이 그를 움직이게 했나?", description="뉴스 헤드라인 추천 내용입니다.")
private String headLine;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.movelog.domain.news.presentation;

import com.movelog.domain.news.application.NewsService;
import com.movelog.domain.news.dto.request.NewsHeadLineReq;
import com.movelog.domain.news.dto.response.HeadLineRes;
import com.movelog.global.config.security.token.CurrentUser;
import com.movelog.global.config.security.token.UserPrincipal;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/news")
@Tag(name = "News", description = "뉴스 관련 API입니다.")
public class NewsController {

private final NewsService newsService;

@Operation(summary = "뉴스 헤드라인 생성 API", description = "뉴스 헤드라인을 생성하는 API입니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "뉴스 헤드라인 생성 성공"),
@ApiResponse(responseCode = "400", description = "뉴스 헤드라인 생성 실패")
})
@PostMapping("/headline")
public List<HeadLineRes> createHeadLine(
@Parameter(description = "Access Token을 입력해주세요.", required = true) @CurrentUser UserPrincipal userPrincipal,
@Parameter(description = "뉴스 헤드라인 생성 요청", required = true) @RequestBody NewsHeadLineReq newsHeadLineReq
) {
return newsService.createHeadLine(userPrincipal, newsHeadLineReq);
}



}
9 changes: 3 additions & 6 deletions src/main/java/com/movelog/domain/record/domain/Record.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ public class Record extends BaseEntity {
@JoinColumn(name = "keyword_id")
private Keyword keyword;

@Column(name = "verb_type")
private Long verbType;
@Enumerated(EnumType.STRING) // Enum을 문자열로 저장
private VerbType verbType;

@Column(name = "record_image")
private String recordImage;
Expand All @@ -38,14 +38,11 @@ public class Record extends BaseEntity {
private java.time.LocalDateTime actionTime;

@Builder
public Record(User user, Keyword keyword, Long verbType, String recordImage) {
public Record(User user, Keyword keyword, VerbType verbType, String recordImage) {
this.user = user;
this.keyword = keyword;
this.verbType = verbType;
this.recordImage = recordImage;
this.actionTime = actionTime == null? LocalDateTime.now():actionTime;
}



}
25 changes: 25 additions & 0 deletions src/main/java/com/movelog/domain/record/domain/VerbType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.movelog.domain.record.domain;

import lombok.Getter;

@Getter
public enum VerbType {
// 했어요, 먹었어요, 갔어요
DO("했어요"), EAT("먹었어요"), GO("갔어요");

private final String verbType;

VerbType(String verbType) {
this.verbType = verbType;
}

// Enum 매핑 메서드 추가
public static VerbType fromValue(String value) {
for (VerbType type : values()) {
if (type.getVerbType().equals(value)) {
return type;
}
}
throw new IllegalArgumentException("No enum constant for value: " + value);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.movelog.domain.record.dto.req;
package com.movelog.domain.record.dto.request;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
Expand All @@ -11,8 +11,8 @@
@NoArgsConstructor
@Getter
public class CreateRecordReq {
@Schema(type = "Long", example = "0", description = "0 -> 했어요, 1 -> 먹었어요, 2 -> 갔어요")
private Long verbType;
@Schema(type = "String", example = "했어요", description = "했어요, 먹었어요, 갔어요")
private String verbType;

@Schema(type = "String", example = "헬스", description = "명사 작성")
private String noun;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.movelog.domain.record.presentation;

import com.movelog.domain.record.dto.req.CreateRecordReq;
import com.movelog.domain.record.repository.RecordRepository;
import com.movelog.domain.record.dto.request.CreateRecordReq;
import com.movelog.domain.record.service.RecordService;
import com.movelog.global.config.security.token.UserPrincipal;
import com.movelog.global.payload.ApiResponse;
Expand Down
Loading

0 comments on commit faebc5a

Please sign in to comment.