Skip to content
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c3f7439
Feat: 데이터 전처리 로직 개발 (#6)
KIMB0B Apr 17, 2025
065f020
Fix: DB 타임존, Store 컬럼 길이 수정 (#8)
KIMB0B Apr 18, 2025
fe88e99
Feat: 트렌드 키워드 검색 로직 구현 (#10)
seyeon22222 Apr 18, 2025
9495d2a
Refactor: 데이터 마이그레이션 로직 변경 (#12)
brobro332 Apr 18, 2025
fd91240
Test: 형태소 분석 후 인기 키워드 추출 테스트 (#14)
KIMB0B Apr 21, 2025
179aed1
feat: 대용량 데이터 처리를 위한 최적화 로직 구현 (#18)
KIMB0B Apr 22, 2025
3888cec
Feat: ExceptionHandler 작성 (#19)
seyeon22222 Apr 22, 2025
04e389c
17 feat 크롤링을 통한 csv파일 읽어오기 (#20)
brobro332 Apr 23, 2025
7c50d35
feat: 스케줄러 도입 및 샤딩 처리 (#22)
brobro332 Apr 24, 2025
076f468
Refactor: Repository에서 가장 최근 분기의 가게만 불러오도록 Query문 수정
minseoBae Apr 24, 2025
66268d2
Rector: 사용자 검색어에 기반한 트렌드 키워드 추출 로직 변경
minseoBae Apr 24, 2025
2ca97e3
feat: 트렌드 키워드의 각 분기별 빈도 수 추출 로직 개발 (#29)
KIMB0B Apr 25, 2025
57ab832
Feat: 상호명 중복 확인 api 만들기 (#30)
brobro332 Apr 25, 2025
37871a4
fix: 인덱싱 작업 중 모든 스레드가 같은 범위의 mysql 데이터를 읽으려고 하는 오류 수정 (#32)
KIMB0B Apr 28, 2025
1ddc56f
Feat: 받아온 검색어, 트렌드 키워드, 사용자 요청 키워드 등을 반영하여 AI를 통해 추천 상호명을 생성하는 로직 개발
minseoBae Apr 29, 2025
5bc86db
feat 배포환경 세팅 (#36)
seyeon22222 Apr 29, 2025
4cbaf5a
Fix: 공공데이터포털 ZIP 파일 압축 해제 안되는 오류 수정 (#39)
brobro332 Apr 29, 2025
333ca7a
Refactor: 공공데이터 저장 지역 서울로 한정 (#41)
brobro332 Apr 29, 2025
80fb137
feat: 사용자 선호 이름 데이터 수집 기능 구현 (#44)
KIMB0B Apr 30, 2025
fd5ff84
fix: elasticsearch에서 search를 통해서 데이터를 가져오는 부분 수정 (#47)
seyeon22222 May 3, 2025
538cb1a
Refactor: 프론트 CORS 전역 설정 변경 (#49)
minseoBae May 4, 2025
437f863
Refactor: 인프라 운영 환경에 맞추어 수정 (#46)
brobro332 May 4, 2025
3cbd56b
Refactor: CI/CD 스크립트 수정 (#53)
brobro332 May 4, 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
43 changes: 43 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Deploy SANGCHU

on:
push:
branches: [ main ]
pull_request:
types: [closed]
branches: [ main ]
workflow_dispatch:

jobs:
deploy:
if: github.event_name == 'push' || github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: SSH into server and deploy
uses: appleboy/[email protected]
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_KEY }}
script: |
if [ -d "/home/ubuntu/LLL3_BACKEND/infra/main_app" ]; then
cd /home/ubuntu/LLL3_BACKEND/infra/main_app
echo "Fetch and reset latest code from main branch..."

git fetch origin main
git reset --hard origin/main

echo "Building Docker service backend..."
docker-compose build --no-cache backend

echo "Starting Docker service backend..."
docker-compose up -d backend

else
echo "Directory /home/ubuntu/LLL3_BACKEND/infra/main_app not found on server!"
exit 1
fi
16 changes: 14 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ build/
.factorypath
.project
.settings
.env
.springBeans
.sts4-cache
bin/
Expand All @@ -27,7 +26,7 @@ bin/
out/
!**/src/main/**/out/
!**/src/test/**/out/
db/
src/main/resources/data/

### NetBeans ###
/nbproject/private/
Expand All @@ -38,3 +37,16 @@ db/

### VS Code ###
.vscode/

### ElasticSearch ###
infra/elasticsearch/data
infra/local/elasticsearch/data/

### local ###
.env
db
elasticsearch
infra/db/
infra/elasticsearch/data/
infra/local/.env
infra/local/db/
21 changes: 18 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ java {
}
}

ext {
set('springAiVersion', "1.0.0-M6")
}

configurations {
compileOnly {
extendsFrom annotationProcessor
Expand All @@ -25,13 +29,18 @@ repositories {

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
// implementation 'org.springframework.boot:spring-boot-starter-batch'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.mysql:mysql-connector-j'
implementation 'org.springframework.boot:spring-boot-starter-batch'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.mysql:mysql-connector-j'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.springframework.boot:spring-boot-starter-cache'
implementation 'io.github.cdimascio:dotenv-java:3.0.0'
implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch'
implementation 'org.springframework.ai:spring-ai-openai-spring-boot-starter'
implementation 'org.seleniumhq.selenium:selenium-java:4.20.0'
implementation 'io.github.bonigarcia:webdrivermanager:5.8.0'
implementation 'org.seleniumhq.selenium:selenium-devtools-v135:4.31.0'
implementation 'com.opencsv:opencsv:5.9'

// Lombok
compileOnly 'org.projectlombok:lombok'
Expand All @@ -48,6 +57,12 @@ dependencies {
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

dependencyManagement {
imports {
mavenBom "org.springframework.ai:spring-ai-bom:${springAiVersion}"
}
}

tasks.named('test') {
useJUnitPlatform()
}
4 changes: 0 additions & 4 deletions elasticsearch/config/elasticsearch.yml

This file was deleted.

76 changes: 67 additions & 9 deletions docker-compose.yml → infra/local/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ services:
elasticsearch:
build:
context: .
dockerfile: Dockerfile_elasticsearch
dockerfile: dockerfile_elasticsearch
container_name: elasticsearch
environment:
- discovery.type=${NODE_TYPE}
Expand All @@ -25,7 +25,9 @@ services:
interval: 30s
timeout: 10s
retries: 5

env_file:
- .env

kibana:
image: kibana:8.12.2
container_name: kibana
Expand All @@ -36,22 +38,27 @@ services:
networks:
- LLL3_network
environment:
ELASTICSEARCH_HOSTS: ${ES_HOST}
ELASTICSEARCH_HOSTS: "http://${ES_HOST}:${ES_EXTERNAL_PORT_1}"
env_file:
- .env

mysql:
image: mysql:8.0
image: mysql:latest
# restart: always
volumes:
- ./conf/my.cnf:/etc/mysql/conf.d/my.cnf
- ./sql:/docker-entrypoint-initdb.d
- ./db/mysql/data:/var/lib/mysql
ports:
- "${DB_INTERNAL_PORT}:${DB_EXTERNAL_PORT}"
- "${MYSQL_EXTERNAL_PORT}:${MYSQL_INTERNAL_PORT}"
environment:
- MYSQL_ROOT_PASSWORD=${DATABASE_ROOT_PASSWORD}
- MYSQL_DATABASE=${DATABASE_NAME}
- MYSQL_USER=${DATABASE_USER}
- MYSQL_PASSWORD=${DATABASE_PASSWORD}
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
- MYSQL_DATABASE=${MYSQL_DB_NAME}
- MYSQL_USER=${MYSQL_USER}
- MYSQL_PASSWORD=${MYSQL_PASSWORD}
- TZ=Asia/Seoul
env_file:
- .env

redis:
image: redis:alpine
Expand All @@ -60,6 +67,57 @@ services:
- "${REDIS_INTERNAL_PORT}:${REDIS_EXTERNAL_PORT}"
networks:
- LLL3_network
env_file:
- .env

backend:
build:
context: ../..
dockerfile: infra/local/dockerfile_backend
container_name: SANGCHU
env_file:
- .env
ports:
- "8080:8081"
depends_on:
- mysql
networks:
- LLL3_network
restart: always

frontend:
build:
context: ../../../LLL3_FRONTEND
dockerfile: Dockerfile
container_name: frontend
ports:
- "3000:80"
networks:
- LLL3_network

nginx:
image: nginx:latest
container_name: nginx
ports:
- "80:80"
volumes:
- ./nginx_config/default.conf:/etc/nginx/conf.d/default.conf:ro
networks:
- LLL3_network
restart: always
env_file:
- .env

embedding:
build:
context: .
dockerfile: dockerfile_embedding
container_name: embedding
ports:
- "5050:5050"
networks:
- LLL3_network
restart: always

networks:
LLL3_network:
Expand Down
29 changes: 29 additions & 0 deletions infra/local/dockerfile_backend
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# 1단계: 빌드 스테이지
FROM gradle:8.7-jdk21 AS builder

# 필요한 소스코드 전체 복사
WORKDIR /build
COPY . .

# Gradle로 jar 빌드 (bootJar task)
RUN gradle bootJar --no-daemon

# 2단계: 실행 스테이지
FROM eclipse-temurin:21-jdk

# vi 설치 (옵션)
RUN apt-get update && apt-get install -y vim && rm -rf /var/lib/apt/lists/*

WORKDIR /app

# 빌드 스테이지에서 생성된 jar 파일 복사
COPY --from=builder /build/build/libs/*.jar app.jar
COPY script/backendSetting.sh /app/wait-for-elasticsearch.sh

RUN chmod 777 /app/app.jar
ARG PROFILE
ENV PROFILE=${PROFILE}

EXPOSE 8080

ENTRYPOINT ["/bin/bash", "/app/wait-for-elasticsearch.sh"]
File renamed without changes.
14 changes: 14 additions & 0 deletions infra/local/dockerfile_embedding
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Dockerfile_embedding

FROM python:3.11-slim

WORKDIR /app

# 필요한 파일 복사
COPY embedding_server/huggingFaceEmbeddingServer.py .

# 필요한 패키지 설치
RUN pip install --no-cache-dir flask numpy requests flask-cors flask-restful flask-socketio sentence_transformers

# 컨테이너 시작 시 python 서버 실행
CMD ["python3", "huggingFaceEmbeddingServer.py"]
32 changes: 32 additions & 0 deletions infra/local/dockerfile_nginx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
FROM nginx:latest

# certbot 설치
RUN apt-get update && apt-get install -y certbot cron

# nginx.conf 파일을 복사하여 설정
COPY nginx_config/default.conf /etc/nginx/conf.d/default.conf

# certbot의 인증서 파일들이 저장될 디렉토리를 볼륨으로 설정
VOLUME ["/etc/letsencrypt", "/var/www/certbot"]

# 필요한 쉘 스크립트 복사
COPY script/nginxSetting.sh start-nginx.sh
COPY nginx_config/config.conf /config.conf

# 쉘 스크립트에 실행 권한 부여
RUN chmod +x start-nginx.sh

# 크론 작업 추가 (80일마다 nginxSetting.sh 실행)
RUN echo "0 0 */80 * * /start-nginx.sh >> /var/log/nginx/cron.log 2>&1" > /etc/cron.d/nginx-cron

# 크론 작업 파일의 권한 설정
RUN chmod 0644 /etc/cron.d/nginx-cron

# cron 서비스 시작 시 로그 디렉토리 생성
RUN mkdir -p /var/log/nginx

# 쉘 스크립트에 실행 권한 부여
RUN chmod +x start-nginx.sh

# nginx 및 cron 데몬을 함께 실행
CMD start-nginx.sh && cron && nginx -g 'daemon off;'
43 changes: 43 additions & 0 deletions infra/local/embedding_server/huggingFaceEmbeddingServer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from flask import Flask, request, jsonify
from sentence_transformers import SentenceTransformer
import numpy as np

app = Flask(__name__)

model = SentenceTransformer("BM-K/KoSimCSE-roberta-multitask")

def cosine_similarity(vec1, vec2):
vec1 = np.array(vec1)
vec2 = np.array(vec2)
return float(np.dot(vec1, vec2) / (np.linalg.norm(vec1) * np.linalg.norm(vec2)))

@app.route("/embed", methods=["POST"])
def embed():
# JSON으로 받은 데이터에서 "keyword" 값을 추출
data = request.get_json()

# 키워드가 없는 경우 처리
keyword = data.get("keyword", "")

if not keyword:
return jsonify({"error": "임베드를 생성할 키워드가 필요합니다."}), 400

# 임베딩
keyword_embedding = model.encode([keyword], convert_to_numpy=True)[0]

# 결과 반환
return jsonify({"embedding": keyword_embedding.tolist()})

@app.route("/embed/batch", methods=["POST"])
def batchEmbed():
data = request.get_json()
keywords = data.get("keywords", [])

if not keywords:
return jsonify({"error": "임베드를 생성할 키워드 리스트가 필요합니다."}), 400

embeddings = model.encode(keywords, convert_to_numpy=True)
return jsonify({"embeddings": [e.tolist() for e in embeddings]})

if __name__ == "__main__":
app.run(host="0.0.0.0", port=5050)
Loading