Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…into feat/account-auto-split
  • Loading branch information
Do760 committed Jun 9, 2024
2 parents 8275aed + e85f52b commit 9ba844a
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 22 deletions.
96 changes: 96 additions & 0 deletions .github/workflows/gradle.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
name: CI/CD using github actions & docker

# event trigger
# main 브랜치에 push가 되었을 때 실행
on:
push:
branches: [ "main" ]

permissions:
contents: read

jobs:
CI-CD:
runs-on: ubuntu-latest
steps:

# JDK 설정
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'

# Gradle 캐시 설정
- name: Gradle Caching
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
# application.yml 파일 생성
- name: make application.yml
run: |
mkdir -p ./src/main/resources
cd ./src/main/resources
touch ./application.yml
echo "spring:" >> ./application.yml
echo " jpa:" >> ./application.yml
echo " database: mysql" >> ./application.yml
echo " hibernate:" >> ./application.yml
echo " ddl-auto: none" >> ./application.yml
echo " show-sql: true" >> ./application.yml
echo " datasource:" >> ./application.yml
echo " url: ${{ secrets.DB_URL }}" >> ./application.yml
echo " username: ${{ secrets.DB_USERNAME }}" >> ./application.yml
echo " password: ${{ secrets.DB_PASSWORD }}" >> ./application.yml
echo " driver-class-name: com.mysql.cj.jdbc.Driver" >> ./application.yml
echo "jwt:" >> ./application.yml
echo " secret: ${{ secrets.JWT_SECRET }}" >> ./application.yml
echo " access-expired-ms: 3600000000" >> ./application.yml
echo " claims:" >> ./application.yml
echo " auth-key: userId" >> ./application.yml
echo "ai:" >> ./application.yml
echo " gemini:" >> ./application.yml
echo " base-url: https://generativelanguage.googleapis.com/v1/models/gemini-1.0-pro-001:generateContent?key=" >> ./application.yml
echo " api-key: ${{ secrets.AI_API_KEY }}" >> ./application.yml
echo "server:" >> ./application.yml
echo " servlet:" >> ./application.yml
echo " encoding:" >> ./application.yml
echo " charset: UTF-8" >> ./application.yml
echo " enabled: true" >> ./application.yml
echo " force: true" >> ./application.yml
shell: bash

# Gradle 빌드
- name: Grant execute permission for gradlew
run: chmod +x ./gradlew

- name: Build with Gradle
run: ./gradlew build -x test

# Docker 빌드 및 푸시
- name: Docker build & push
run: |
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
docker build -f Dockerfile -t ${{ secrets.DOCKER_USERNAME }}/hana_piece .
docker push ${{ secrets.DOCKER_USERNAME }}/hana_piece
# 배포
- name: Deploy
uses: appleboy/ssh-action@master
id: deploy
with:
host: ${{ secrets.HOST }}
username: ubuntu
key: ${{ secrets.PRIVATE_KEY }}
script: |
sudo docker ps
sudo docker pull ${{ secrets.DOCKER_USERNAME }}/hana_piece
sudo docker run -d -p 8080:8080 ${{ secrets.DOCKER_USERNAME }}/hana_piece
sudo docker image prune -f
16 changes: 16 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Dockerfile

# jdk17 Image Start
FROM openjdk:17

# 인자 설정 - JAR_File
ARG JAR_FILE=build/libs/*.jar

# jar 파일 복제
COPY ${JAR_FILE} app.jar

# 인자 설정 부분과 jar 파일 복제 부분 합쳐서 진행해도 무방
#COPY build/libs/*.jar app.jar

# 실행 명령어
ENTRYPOINT ["java", "-jar", "app.jar"]
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
import com.project.hana_piece.account.domain.AccountTransaction;
import com.project.hana_piece.common.util.LocalDateTimeUtil;

public record AccountDailyTransactionGetResponse(Integer transactionDay, Long amount, String accountTransactionType) {
public record AccountDailyTransactionGetResponse(Integer transactionDay, Long amount, String accountTransactionType,
String targetNm) {

public static AccountDailyTransactionGetResponse fromEntity(AccountTransaction entity) {
return new AccountDailyTransactionGetResponse(LocalDateTimeUtil.localDateTimeToDayFormat(entity.getCreatedAt()), entity.getAmount(), entity.getAccountTransactionTypeCd());
return new AccountDailyTransactionGetResponse(LocalDateTimeUtil.localDateTimeToDayFormat(entity.getCreatedAt()), entity.getAmount(), entity.getAccountTransactionTypeCd(), entity.getTargetNm());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,8 @@
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.*;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
Expand All @@ -49,6 +47,7 @@ public List<ProductGetResponse> findProducts() {
.toList();
}


public RecommendationResponse recommendProducts(Long userGoalId) {
Optional<UserGoal> userGoalOptional = userGoalRepository.findById(userGoalId);
if (userGoalOptional.isEmpty()) {
Expand All @@ -64,15 +63,20 @@ public RecommendationResponse recommendProducts(Long userGoalId) {
.map(enrolledProduct -> enrolledProduct.getProduct().getProductId())
.toList();

// 추천 상품 목록 생성
String promptMessage = buildPromptMessage(userGoal, products, enrolledProductIds);
GeminiPrompt geminiPrompt = GeminiPrompt.builder().requests(promptMessage).build();
// 추천 상품 목록 생성
GeminiPrompt geminiPrompt = buildPromptMessage(userGoal, products, enrolledProductIds);
GeminiCallResponse aiResponse = aiService.callGenerativeLanguageApi(geminiPrompt);
String aiResponseMessage = aiResponse.message();
String[] productIdStringList = aiResponseMessage.split(",");
List<Long> productIdList = Arrays.stream(productIdStringList)
.map(string -> Long.valueOf(string.trim()))
.filter(productId -> !enrolledProductIds.contains(productId))
.filter(productId -> {
if (!"HOUSE".equals(userGoal.getGoalTypeCd()) && productId == 14) {
return false;
}
return true;
})
.toList();

List<ProductGetResponse> recommendedProducts = new ArrayList<>();
Expand All @@ -91,27 +95,45 @@ public RecommendationResponse recommendProducts(Long userGoalId) {
return new RecommendationResponse(recommendedProducts, enrolledProductResponses);
}

private String buildPromptMessage(UserGoal userGoal, List<Product> products, List<Long> enrolledProductIds) {
StringBuilder promptMessage = new StringBuilder();
promptMessage.append("아래의 상품 리스트 중 ")
.append(userGoal.getGoalTypeCd())
.append("을 위한 하나은행 적금을 최소 7개 추천해줘. ")
.append("목표 금액은 ").append(userGoal.getAmount()).append("원이고, 시작 날짜는 ").append(userGoal.getGoalBeginDate())
.append("이며, 목표를 달성하기 위한 기간은 ").append(userGoal.getDuration()).append("년이다. ")
.append("답변으로는 추천 상품의 product_id만 콤마를 기준으로 공백없이 나열해서 응답해주면 돼. ")
.append("이 내용의 텍스트만 전달해줘. ")
.append("아래는 상품 리스트야: ");
private GeminiPrompt buildPromptMessage(UserGoal userGoal, List<Product> products, List<Long> enrolledProductIds) {
String requests = "아래의 상품 리스트 중 " + userGoal.getGoalAlias() + "을 위한 하나은행 적금을 최소 7개 추천해줘. "
+ "목표 금액은 " + userGoal.getAmount() + "원이고, 시작 날짜는 " + userGoal.getGoalBeginDate()
+ "이며, 목표를 달성하기 위한 기간은 " + userGoal.getDuration() + "개월이다. ";

StringBuilder constraintsBuilder = new StringBuilder();
constraintsBuilder.append("답변으로는 추천 상품의 product_id만 콤마를 기준으로 공백없이 나열해서 응답해줘. ")
.append("상품 ID는 이미 등록된 적금 상품 ID를 제외하고 추천해야 해. ");

if ("HOUSE".equals(userGoal.getGoalTypeCd())) {
constraintsBuilder.append("goal_type_cd가 HOUSE인 경우 청년 주택드림 청약통장(ProductId: 14)을 포함해야 해. ");
} else {
constraintsBuilder.append("goal_type_cd가 HOUSE가 아닌 경우 청년 주택드림 청약통장(ProductId: 14)을 추천하지 말아야 해. ");
}

if ("CAR".equals(userGoal.getGoalTypeCd())) {
constraintsBuilder.append("goal_type_cd가 CAR인 경우, 높은 금리와 유연한 적립 조건을 갖춘 상품을 고려해줘. ");
}

StringBuilder exampleDataBuilder = new StringBuilder("아래는 상품 리스트야: ");
products.stream()
.filter(product -> !enrolledProductIds.contains(product.getProductId()))
.forEach(product -> promptMessage.append("ProductId: ").append(product.getProductId())
.forEach(product -> exampleDataBuilder.append("ProductId: ").append(product.getProductId())
.append(", ProductName: ").append(product.getProductNm())
.append(", ProductTermYear: ").append(product.getTermYear())
.append(", ProductInterestRate: ").append(product.getInterestRate()).append("; "));
.append(", ProductInterestRate: ").append(product.getInterestRate())
.append("; "));

String responseFormat = "추천 상품의 product_id만 콤마를 기준으로 공백없이 나열해서 응답해줘.";

return promptMessage.toString();
return GeminiPrompt.builder()
.requests(requests)
.constraints(constraintsBuilder.toString())
.responseFormat(responseFormat)
.exampleData(exampleDataBuilder.toString())
.build();
}


public ProductDetailResponse getProductDetail(Long productId) {
Optional<Product> productOptional = productRepository.findById(productId);
if (productOptional.isEmpty()) {
Expand Down

0 comments on commit 9ba844a

Please sign in to comment.