store update

This commit is contained in:
youbeen 2025-06-12 18:17:44 +09:00
parent 4946be1f49
commit c281299d94
10 changed files with 292 additions and 196 deletions

View File

@ -1,110 +1,110 @@
name: Analytics CI name: Analytics CI
on: on:
push: push:
branches: [ main, develop ] branches: [ main, develop ]
paths: paths:
- 'analytics/**' - 'analytics/**'
- 'common/**' - 'common/**'
- 'build.gradle' - 'build.gradle'
- 'settings.gradle' - 'settings.gradle'
pull_request: pull_request:
branches: [ main ] branches: [ main ]
paths: paths:
- 'analytics/**' - 'analytics/**'
- 'common/**' - 'common/**'
- 'build.gradle' - 'build.gradle'
- 'settings.gradle' - 'settings.gradle'
workflow_dispatch: workflow_dispatch:
env: env:
ACR_NAME: acrdigitalgarage03 ACR_NAME: acrdigitalgarage03
IMAGE_NAME: hiorder/analytics IMAGE_NAME: hiorder/analytics
jobs: jobs:
build-and-push: build-and-push:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set up JDK 21 - name: Set up JDK 21
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
java-version: '21' java-version: '21'
distribution: 'temurin' distribution: 'temurin'
- name: Setup Gradle - name: Setup Gradle
uses: gradle/actions/setup-gradle@v3 uses: gradle/actions/setup-gradle@v3
with: with:
gradle-version: '8.13' gradle-version: '8.13'
- name: Cache Gradle packages - name: Cache Gradle packages
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
path: | path: |
~/.gradle/caches ~/.gradle/caches
~/.gradle/wrapper ~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: | restore-keys: |
${{ runner.os }}-gradle- ${{ runner.os }}-gradle-
- name: Generate Gradle Wrapper - name: Generate Gradle Wrapper
run: | run: |
echo "Generating gradle wrapper..." echo "Generating gradle wrapper..."
gradle wrapper --gradle-version 8.13 gradle wrapper --gradle-version 8.13
chmod +x gradlew chmod +x gradlew
echo "Testing gradle wrapper..." echo "Testing gradle wrapper..."
./gradlew --version ./gradlew --version
- name: Build analytics module with dependencies - name: Build analytics module with dependencies
run: ./gradlew analytics:build -x test run: ./gradlew analytics:build -x test
- name: Run analytics tests - name: Run analytics tests
run: ./gradlew analytics:test run: ./gradlew analytics:test
- name: Generate build timestamp - name: Generate build timestamp
id: timestamp id: timestamp
run: echo "BUILD_TIME=$(date +'%y%m%d%H%M')" >> $GITHUB_OUTPUT run: echo "BUILD_TIME=$(date +'%y%m%d%H%M')" >> $GITHUB_OUTPUT
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: Log in to Azure Container Registry - name: Log in to Azure Container Registry
uses: azure/docker-login@v1 uses: azure/docker-login@v1
with: with:
login-server: ${{ env.ACR_NAME }}.azurecr.io login-server: ${{ env.ACR_NAME }}.azurecr.io
username: ${{ secrets.ACR_USERNAME }} username: ${{ secrets.ACR_USERNAME }}
password: ${{ secrets.ACR_PASSWORD }} password: ${{ secrets.ACR_PASSWORD }}
- name: Build and push Docker image - name: Build and push Docker image
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
with: with:
context: . context: .
file: ./analytics/Dockerfile file: ./analytics/Dockerfile
platforms: linux/amd64 platforms: linux/amd64
push: true push: true
tags: | tags: |
${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}:${{ steps.timestamp.outputs.BUILD_TIME }} ${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}:${{ steps.timestamp.outputs.BUILD_TIME }}
${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}:latest ${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}:latest
- name: Output image tags - name: Output image tags
run: | run: |
echo "🎉 Image pushed successfully!" echo "🎉 Image pushed successfully!"
echo "📦 Image: ${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}" echo "📦 Image: ${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}"
echo "🏷️ Tags: ${{ steps.timestamp.outputs.BUILD_TIME }}, latest" echo "🏷️ Tags: ${{ steps.timestamp.outputs.BUILD_TIME }}, latest"
- name: Upload test results - name: Upload test results
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
if: always() if: always()
with: with:
name: analytics-test-results name: analytics-test-results
path: analytics/build/reports/tests/test/ path: analytics/build/reports/tests/test/
- name: Upload build artifacts - name: Upload build artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
if: success() if: success()
with: with:
name: analytics-jar name: analytics-jar
path: analytics/build/libs/*.jar path: analytics/build/libs/*.jar

View File

@ -1,51 +1,51 @@
# Analytics 서비스용 Dockerfile # Analytics 서비스용 Dockerfile
FROM eclipse-temurin:21-jdk-alpine AS builder FROM eclipse-temurin:21-jdk-alpine AS builder
# 작업 디렉토리 설정 # 작업 디렉토리 설정
WORKDIR /app WORKDIR /app
# Gradle Wrapper와 설정 파일 복사 # Gradle Wrapper와 설정 파일 복사
COPY gradlew . COPY gradlew .
COPY gradle/wrapper gradle/wrapper COPY gradle/wrapper gradle/wrapper
COPY build.gradle . COPY build.gradle .
COPY settings.gradle . COPY settings.gradle .
# 소스 코드 복사 # 소스 코드 복사
COPY common common/ COPY common common/
COPY analytics analytics/ COPY analytics analytics/
# 실행 권한 부여 및 빌드 # 실행 권한 부여 및 빌드
RUN chmod +x ./gradlew RUN chmod +x ./gradlew
RUN ./gradlew analytics:build -x test --no-daemon RUN ./gradlew analytics:build -x test --no-daemon
# 실행 단계 # 실행 단계
FROM eclipse-temurin:21-jre-alpine FROM eclipse-temurin:21-jre-alpine
# 애플리케이션 사용자 생성 # 애플리케이션 사용자 생성
RUN addgroup -g 1001 -S appgroup && \ RUN addgroup -g 1001 -S appgroup && \
adduser -u 1001 -S appuser -G appgroup adduser -u 1001 -S appuser -G appgroup
# 작업 디렉토리 설정 # 작업 디렉토리 설정
WORKDIR /app WORKDIR /app
# 빌드된 JAR 파일 복사 # 빌드된 JAR 파일 복사
COPY --from=builder /app/analytics/build/libs/analytics-*.jar app.jar COPY --from=builder /app/analytics/build/libs/analytics-*.jar app.jar
# 파일 소유권 변경 # 파일 소유권 변경
RUN chown -R appuser:appgroup /app RUN chown -R appuser:appgroup /app
# 사용자 변경 # 사용자 변경
USER appuser USER appuser
# 포트 노출 # 포트 노출
EXPOSE 8080 EXPOSE 8080
# 헬스체크 추가 # 헬스체크 추가
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1 CMD wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1
# JVM 옵션 설정 # JVM 옵션 설정
ENV JAVA_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:+UseStringDeduplication" ENV JAVA_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:+UseStringDeduplication"
# 애플리케이션 실행 # 애플리케이션 실행
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"] ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

View File

@ -1,5 +1,6 @@
package com.ktds.hi.analytics.infra.config; package com.ktds.hi.analytics.infra.config;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing; import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@ -9,7 +10,15 @@ import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
* JPA Auditing Repository 스캔 설정 * JPA Auditing Repository 스캔 설정
*/ */
@Configuration @Configuration
@EnableJpaAuditing @EnableJpaRepositories(basePackages = {
@EnableJpaRepositories(basePackages = "com.ktds.hi.analytics.infra.gateway.repository") // "com.ktds.hi.store.infra.gateway.entity",
"com.ktds.hi.common.repository"
})
@EntityScan(basePackages = {
"com.ktds.hi.store.infra.gateway.entity",
"com.ktds.hi.common.entity",
"com.ktds.hi.common.audit"
})
@EnableJpaAuditing(auditorAwareRef = "customAuditorAware")
public class JpaConfig { public class JpaConfig {
} }

View File

@ -14,6 +14,7 @@ import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
// import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer;

View File

@ -15,7 +15,7 @@ import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
"com.ktds.hi.common.entity", "com.ktds.hi.common.entity",
"com.ktds.hi.common.audit" "com.ktds.hi.common.audit"
}) })
@EnableJpaRepositories(basePackages = "com.ktds.hi.common.repository") // @EnableJpaRepositories(basePackages = "com.ktds.hi.common.repository")
public class CommonModuleConfiguration { public class CommonModuleConfiguration {
// 설정 클래스는 어노테이션만으로도 충분 // 설정 클래스는 어노테이션만으로도 충분
} }

View File

@ -1,33 +1,44 @@
package com.ktds.hi.store; package com.ktds.hi.store;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing; import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.web.client.RestTemplate; import org.springframework.context.annotation.Bean;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
/** import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
* 추천 서비스 메인 애플리케이션 클래스 import org.springframework.web.client.RestTemplate;
* 가게 추천, 취향 분석 기능을 제공
* /**
* @author 하이오더 개발팀 * 추천 서비스 메인 애플리케이션 클래스
* @version 1.0.0 * 가게 추천, 취향 분석 기능을 제공
*/ *
@SpringBootApplication(scanBasePackages = { * @author 하이오더 개발팀
"com.ktds.hi.store", * @version 1.0.0
"com.ktds.hi.common" */
}) @SpringBootApplication(scanBasePackages = {
"com.ktds.hi.store",
@EnableJpaAuditing "com.ktds.hi.common"
public class StoreApplication { })
@EnableJpaRepositories(basePackages = {
public static void main(String[] args) { "com.ktds.hi.store.infra.gateway.repository", // 👈 MenuJpaRepository 패키지
SpringApplication.run(StoreApplication.class, args); "com.ktds.hi.common.repository"
} })
@EntityScan(basePackages = {
// 👈 부분만 추가 "com.ktds.hi.store.infra.gateway.entity",
@Bean "com.ktds.hi.common.entity"
public RestTemplate restTemplate() { })
return new RestTemplate(); @EnableJpaAuditing(auditorAwareRef = "customAuditorAware")
} public class StoreApplication {
}
public static void main(String[] args) {
SpringApplication.run(StoreApplication.class, args);
}
// 기존 빈들...
@Bean
@ConditionalOnMissingBean(RestTemplate.class) // 👈 다른 빈이 없을 때만 생성
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

View File

@ -0,0 +1,46 @@
package com.ktds.hi.store.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Value("${spring.data.redis.host:localhost}")
private String redisHost;
@Value("${spring.data.redis.port:6379}")
private int redisPort;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(redisHost, redisPort);
}
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// 직렬화 설정
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
template.afterPropertiesSet();
return template;
}
@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory connectionFactory) {
return new StringRedisTemplate(connectionFactory);
}
}

View File

@ -0,0 +1,26 @@
package com.ktds.hi.store.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.web.client.RestTemplate;
import org.springframework.boot.web.client.RestTemplateBuilder;
import java.time.Duration;
@Configuration
public class RestTemplateConfig {
@Bean
@Primary
public RestTemplate restTemplate() {
return new RestTemplate();
}
// 또는 나은 설정으로
@Bean("configuredRestTemplate")
public RestTemplate configuredRestTemplate(RestTemplateBuilder builder) {
return builder.connectTimeout(Duration.ofSeconds(5)).readTimeout(Duration.ofSeconds(10))
.build();
}
}

View File

@ -1,6 +1,7 @@
package com.ktds.hi.store.infra.gateway; package com.ktds.hi.store.infra.gateway;
import com.ktds.hi.store.biz.usecase.out.CachePort; import com.ktds.hi.store.biz.usecase.out.CachePort;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
@ -15,6 +16,7 @@ import java.util.Optional;
*/ */
@Component @Component
@RequiredArgsConstructor @RequiredArgsConstructor
// @NoArgsConstructor(force = true)
@Slf4j @Slf4j
public class CacheAdapter implements CachePort { public class CacheAdapter implements CachePort {

View File

@ -9,6 +9,7 @@ import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener; import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.math.BigDecimal;
import java.time.LocalDateTime; import java.time.LocalDateTime;
/** /**