Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
4cf4e1e
refactor: test code 간편화를 위한 UserEntityBuilder 클래스 작성
rodom1018 Jan 4, 2025
77f41b1
chore: local 프로필에 CLOVA Studio 'AI 질문 생성기' API 관련 변수 설정
seop-h Jan 5, 2025
a0dbf43
rename: Question 관련 상위 디렉터리 생성 및 기존 파일 이동
seop-h Jan 5, 2025
0d70b0c
feat: admin 페이지 ip 검사 로직 추가(필터 추가)
rodom1018 Jan 6, 2025
cb905b7
build: OpenFeign 디펜던시 추가
seop-h Jan 6, 2025
8d67980
chore: task-id -> url 로 변수 변경
seop-h Jan 6, 2025
d28aa39
feat: OpenFeign 설정 및 외부 API와의 통신 클라이언트 생성
seop-h Jan 6, 2025
2396f99
feat: 스케줄러가 멀티 스레드 환경에서 동작하도록 설정
seop-h Jan 7, 2025
c992a37
feat: 매일 자정마다 질문을 생성 및 저장하는 기능 추가
seop-h Jan 7, 2025
64f78e5
chore: release, test 프로필에 CLOVA Studio 'AI 질문 생성기' API 관련 변수 설정
seop-h Jan 7, 2025
bd0ba5b
remove: 필요없는 메서드 삭제
seop-h Jan 7, 2025
7124cc1
comment: 주석 추가
seop-h Jan 7, 2025
e68c1b3
comment: 주석 추가
seop-h Jan 7, 2025
f19cb25
ci: 빌드 성공 시 코멘트 작성 부분 삭제
seop-h Jan 7, 2025
d802251
rename: 변수 및 클래스 이름 변경
seop-h Jan 8, 2025
0fdccd2
Merge pull request #111 from seop-h/feature/question-creation
seop-h Jan 8, 2025
0a825e9
feat: AES-256을 사용하는 Encryptor 추가
seop-h Jan 10, 2025
6efc075
feat: Attribute Converter를 이용하여 note content 컨버팅
seop-h Jan 10, 2025
4d33543
test: Converter 추가로 인한 변경 사항 테스트 코드에 반영
seop-h Jan 10, 2025
20c7072
Merge pull request #112 from seop-h/feature/note-encypt
seop-h Jan 10, 2025
193069a
feat: 소셜 로그인 단위 테스트 작성 및 try catch 문 삭제하고 @RestControllerService 사용
rodom1018 Jan 11, 2025
e0a833e
Merge branch 'develop' into refactor-account
rodom1018 Jan 11, 2025
c9ab20c
Merge pull request #113 from woozuda/refactor-account
rodom1018 Jan 12, 2025
c620d65
feat: aop(시간 측정) 어노테이션 제작
rodom1018 Jan 14, 2025
f271ba2
Merge pull request #114 from woozuda/feature-aop
rodom1018 Jan 14, 2025
86f6f8d
feat: 다이어리/일기를 생성/수정/삭제 후 이미지 테이블 변경 로직 작성, 테스트 코드 작성
rodom1018 Jan 16, 2025
6582ba6
feat: 다이어리/일기 조작 시 이미지 테이블 조작 함수 삽입
rodom1018 Jan 16, 2025
fe3672d
feat: 매일 오전 3시에 이미지 정리 크론 잡 생성
rodom1018 Jan 16, 2025
c95d30b
Merge pull request #115 from woozuda/feature-image-advanced
rodom1018 Jan 17, 2025
484e838
feat: 숏링크 테스트 코드 작성
rodom1018 Jan 17, 2025
a2ff279
Merge pull request #116 from woozuda/test-code
rodom1018 Jan 17, 2025
68af285
feat : 네이버 클로바 환경설정 및 비동기 처리 방식 설정
baesaa0304 Feb 13, 2025
4bc7768
fix : 임포트 수정
baesaa0304 Feb 13, 2025
965e612
fix : 오류 수정
baesaa0304 Feb 13, 2025
7ffb73b
Merge pull request #117 from woozuda/feature-ai-v11
baesaa0304 Feb 14, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 1 addition & 15 deletions .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,7 @@ jobs:
./gradlew clean build
shell: bash

# 5. 빌드 성공 시 코멘트
- name: Comment on PR if Build Succeed
if: success()
uses: actions/github-script@v7
with:
script: |
const { owner, repo, number } = context.issue;
await github.rest.issues.createComment({
owner,
repo,
issue_number: number,
body: `✅ 빌드 테스트가 성공적으로 완료되었습니다!`
});

# 6. 빌드 실패 시 코멘트
# 5. 빌드 실패 시 코멘트
- name: Comment on PR if Build Fail
if: failure()
uses: actions/github-script@v7
Expand Down
17 changes: 17 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ plugins {
id 'idea'
}

ext {
springCloudVersion = "2023.0.4"
}

group = 'com.woozuda'
version = '0.0.1-SNAPSHOT'

Expand Down Expand Up @@ -35,6 +39,7 @@ repositories {
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-webflux'
// Apache HttpClient 의존성 추가
implementation 'org.apache.httpcomponents:httpclient:4.5.13'
// Jackson 라이브러리 (JSON 파싱을 위한)
Expand Down Expand Up @@ -86,12 +91,24 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
//prometheus(metrics)
runtimeOnly 'io.micrometer:micrometer-registry-prometheus'

//OpenFeign: 외부 API 사용
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign:4.1.4'

// html 파싱을 위한 jsoup
implementation 'org.jsoup:jsoup:1.16.1'
}

tasks.named('test') {
useJUnitPlatform()
}

dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:$springCloudVersion"
}
}


//QueryDSL 설정부 시작
/*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.woozuda.backend.account.controller;


import com.woozuda.backend.aop.LogExecutionTime;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

Expand All @@ -12,12 +13,12 @@ public String adminP(){
return "admin controller";
}

@LogExecutionTime
@GetMapping("/account/sample/alluser")
public String allP(){
return "all user can access this page!";
}

@GetMapping("/account/sample/user")
public String userP(){ return "user can access this page! ";}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import com.woozuda.backend.account.dto.JoinDTO;
import com.woozuda.backend.account.service.JoinService;
import com.woozuda.backend.exception.InvalidEmailException;
import com.woozuda.backend.exception.UsernameAlreadyExistsException;
import com.woozuda.backend.exception.account.InvalidEmailException;
import com.woozuda.backend.exception.account.UsernameAlreadyExistsException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
Expand All @@ -21,15 +21,7 @@ public JoinController(JoinService joinService){

@PostMapping("/join")
public ResponseEntity<Void> joinProcess(@RequestBody JoinDTO joinDTO){

try {
joinService.joinProcess(joinDTO);
}catch(InvalidEmailException e){
return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).build();
}catch(UsernameAlreadyExistsException e) {
return ResponseEntity.status(HttpStatus.CONFLICT).build();
}

joinService.joinProcess(joinDTO);
return ResponseEntity.status(HttpStatus.OK).build();
}
}
7 changes: 7 additions & 0 deletions src/main/java/com/woozuda/backend/account/dto/JoinDTO.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
package com.woozuda.backend.account.dto;


import com.woozuda.backend.account.entity.AiType;
import com.woozuda.backend.account.entity.UserEntity;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@AllArgsConstructor
public class JoinDTO {
String username;
String password;

public static JoinDTO transDTO(UserEntity userEntity){
return new JoinDTO(userEntity.getUsername(), userEntity.getPassword());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,6 @@ public class UserEntity extends BaseTimeEntity {
private String provider;

public static UserEntity transEntity(JoinDTO joinDTO){
return new UserEntity(null, joinDTO.getUsername(), joinDTO.getPassword(), "ROLE_ADMIN", AiType.PICTURE_NOVEL, true, joinDTO.getUsername(), "woozuda");
return new UserEntity(null, joinDTO.getUsername(), joinDTO.getPassword(), "ROLE_USER", AiType.PICTURE_NOVEL, true, joinDTO.getUsername(), "woozuda");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic

if (existData == null) {

UserEntity userEntity = new UserEntity(null, username, null, "ROLE_ADMIN", AiType.PICTURE_NOVEL, true, oAuth2Response.getEmail(), oAuth2Response.getProvider());
UserEntity userEntity = new UserEntity(null, username, null, "ROLE_USER", AiType.PICTURE_NOVEL, true, oAuth2Response.getEmail(), oAuth2Response.getProvider());
userRepository.save(userEntity);
shortLinkUtil.saveShortLink(userEntity);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundEx
log.info("로그인 디버깅 로그 - ");
log.info(username);
UserEntity userData = userRepository.findByUsername(username);

if(userData != null){
return new CustomUser(userData);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@
import com.woozuda.backend.account.dto.JoinDTO;
import com.woozuda.backend.account.entity.UserEntity;
import com.woozuda.backend.account.repository.UserRepository;
import com.woozuda.backend.exception.InvalidEmailException;
import com.woozuda.backend.exception.UsernameAlreadyExistsException;
import com.woozuda.backend.exception.account.InvalidEmailException;
import com.woozuda.backend.exception.account.UsernameAlreadyExistsException;
import com.woozuda.backend.shortlink.util.ShortLinkUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -28,7 +27,7 @@ public class JoinService {
private final ShortLinkUtil shortLinkUtil;

@Transactional
public void joinProcess(JoinDTO joinDTO){
public JoinDTO joinProcess(JoinDTO joinDTO){

if(!isValidEmail(joinDTO.getUsername())){
throw new InvalidEmailException("잘못된 이메일 형식을 입력 했습니다");
Expand All @@ -46,15 +45,15 @@ public void joinProcess(JoinDTO joinDTO){
UserEntity data = UserEntity.transEntity(joinDTO);

//레포지터리에 entity를 저장합니다
userRepository.save(data);
UserEntity newUser = userRepository.save(data);

// 유저에 대한 숏링크 제작
shortLinkUtil.saveShortLink(data);


return JoinDTO.transDTO(newUser);
}

public static boolean isValidEmail(String username){
public boolean isValidEmail(String username){

// 이메일 주소 형식이 아닌 경우 false
String regex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$";
Expand Down
38 changes: 11 additions & 27 deletions src/main/java/com/woozuda/backend/ai/config/ChatGPTConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.client.RestTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.web.reactive.function.client.WebClient;

/**
* ChatGPT API 통합을 위한 설정 클래스입니다.
Expand All @@ -20,33 +20,17 @@ public class ChatGPTConfig {
@Value("${openai.api.key}")
private String apiKey;

/**
* RestTemplate 빈 설정.
* ChatGPT API와 같은 외부 서비스에 HTTP 요청을 보낼 때 사용됩니다.
*
* @return RestTemplate 인스턴스
*/
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}

/**
* ChatGPT API 요청에 사용되는 HTTP 헤더를 설정
*
* @return HttpHeaders 인스턴스
*/
@Bean
public HttpHeaders httpHeaders() {
HttpHeaders headers = new HttpHeaders();
@Value("${openai.api.url}")
private String apiUrl;


// API 키를 x-api-key 헤더에 추가
headers.set("Authorization", "Bearer " + apiKey);

// 요청 본문을 JSON 형식으로 설정
headers.setContentType(MediaType.APPLICATION_JSON);

return headers;
@Bean
public WebClient webClient() {
return WebClient.builder()
.baseUrl(apiUrl) // API base URL 설정
.defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey) // Authorization 헤더 설정
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) // Content-Type 설정
.build();
}

}
47 changes: 17 additions & 30 deletions src/main/java/com/woozuda/backend/ai/config/ChatGptService.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

import java.util.Map;

Expand All @@ -16,10 +16,11 @@ public class ChatGptService {
/**
* chat GPT 연동하는 서비스 로직 입니다.
*/
private final RestTemplate restTemplate;

@Value("${openai.api.key}")
private String apiKey;
private final WebClient webClient;

@Value("${openai.api.url}")
private String apiUrl;

private static final int MAX_CONTEXT_TOKENS = 4096; // max 토큰 값 설정

Expand All @@ -34,8 +35,6 @@ public String analyzeDiaryUsingGPT(String systemMessage, String userMessage) {
// maxTokens가 0보다 작지 않도록 확인
maxTokens = Math.max(maxTokens, 50); // 최소 50 토큰은 남겨두기

String apiUrl = "https://api.openai.com/v1/chat/completions"; // 실제 ChatGPT API URL

// 요청 데이터 구성
Map<String, Object> requestBody = Map.of(
"model", "gpt-3.5-turbo", // GPT 3.5 모델 사용
Expand All @@ -46,34 +45,22 @@ public String analyzeDiaryUsingGPT(String systemMessage, String userMessage) {
"max_tokens", maxTokens // 응답에 사용할 최대 토큰 수 설정
);

// HttpHeader에 추가
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + apiKey);
//headers.setContentType(org.springframework.http.MediaType.APPLICATION_JSON);
headers.setContentType(MediaType.APPLICATION_JSON);

// HttpEntity로 요청과 헤더를 묶어서 전송
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(requestBody, headers);

try {
// HTTP 요청 보내기
ResponseEntity<String> response = restTemplate.exchange(apiUrl, HttpMethod.POST, entity, String.class);

// API 응답이 성공적일 경우 반환된 데이터 리턴
if (response.getStatusCode() == HttpStatus.OK) {
return response.getBody();
} else {
throw new RuntimeException("AI 호출 실패: " + response.getStatusCode());
}
} catch (Exception e) {
throw new RuntimeException("AI 호출 중 오류 발생: " + e.getMessage());

}
// WebClient로 요청 보내기
Mono<String> responseMono = webClient.post()
.uri(apiUrl) // 실제 엔드포인트 경로로 수정
.bodyValue(requestBody)
.retrieve()
.bodyToMono(String.class);

// 동기적으로 응답을 기다리기 위해 block() 사용
return responseMono.block();
}

private int estimateTokenCount(String text) {
// 간단한 토큰 계산: 한글 기준으로 1.5~2배로 계산
return text.split("\\s+").length * 2; // 한 단어에 대해 2토큰으로 계산
}

}


Expand Down
Loading