recommend
This commit is contained in:
parent
1a0d3c5268
commit
ff127c1edc
62
build.gradle
62
build.gradle
@ -1,13 +1,13 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'java'
|
id 'java'
|
||||||
id 'org.springframework.boot' version '3.4.0' apply false
|
id 'org.springframework.boot' version '3.4.0' apply false
|
||||||
id 'io.spring.dependency-management' version '1.1.4' apply false
|
id 'io.spring.dependency-management' version '1.1.6' apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
group = 'com.ktds.hi'
|
group = 'com.ktds.hi'
|
||||||
version = '1.0.0'
|
version = '1.0.0'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
@ -17,49 +17,37 @@ subprojects {
|
|||||||
apply plugin: 'java'
|
apply plugin: 'java'
|
||||||
apply plugin: 'org.springframework.boot'
|
apply plugin: 'org.springframework.boot'
|
||||||
apply plugin: 'io.spring.dependency-management'
|
apply plugin: 'io.spring.dependency-management'
|
||||||
|
|
||||||
java {
|
java {
|
||||||
toolchain {
|
sourceCompatibility = '21'
|
||||||
languageVersion = JavaLanguageVersion.of(21)
|
}
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
compileOnly {
|
||||||
|
extendsFrom annotationProcessor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 공통 의존성
|
||||||
dependencies {
|
dependencies {
|
||||||
// 공통 의존성
|
// Lombok (모든 서브 프로젝트에서 사용)
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-actuator'
|
|
||||||
|
|
||||||
// Swagger
|
|
||||||
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'
|
|
||||||
|
|
||||||
// JWT
|
|
||||||
implementation 'io.jsonwebtoken:jjwt-api:0.12.3'
|
|
||||||
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.3'
|
|
||||||
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.3'
|
|
||||||
|
|
||||||
// Database
|
|
||||||
runtimeOnly 'org.postgresql:postgresql'
|
|
||||||
|
|
||||||
// Lombok
|
|
||||||
compileOnly 'org.projectlombok:lombok'
|
compileOnly 'org.projectlombok:lombok'
|
||||||
annotationProcessor 'org.projectlombok:lombok'
|
annotationProcessor 'org.projectlombok:lombok'
|
||||||
|
|
||||||
// MapStruct
|
// 테스트 (모든 서브 프로젝트에서 사용)
|
||||||
implementation 'org.mapstruct:mapstruct:1.5.5.Final'
|
|
||||||
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final'
|
|
||||||
|
|
||||||
// Test
|
|
||||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||||
testImplementation 'org.springframework.security:spring-security-test'
|
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||||
testImplementation 'org.testcontainers:junit-jupiter'
|
|
||||||
testImplementation 'org.testcontainers:postgresql'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 컴파일 옵션
|
||||||
|
tasks.withType(JavaCompile) {
|
||||||
|
options.encoding = 'UTF-8'
|
||||||
|
options.compilerArgs += ['-parameters']
|
||||||
|
}
|
||||||
|
|
||||||
|
// 테스트 설정
|
||||||
tasks.named('test') {
|
tasks.named('test') {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
|
systemProperty 'spring.profiles.active', 'test'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
plugins {
|
||||||
|
id 'java-library'
|
||||||
|
}
|
||||||
|
|
||||||
|
description = 'Common utilities and shared components'
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
// Spring Boot Starters
|
||||||
|
api 'org.springframework.boot:spring-boot-starter-web'
|
||||||
|
api 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||||
|
api 'org.springframework.boot:spring-boot-starter-validation'
|
||||||
|
api 'org.springframework.boot:spring-boot-starter-security'
|
||||||
|
|
||||||
|
// AOP (AspectJ) - 명시적 의존성 추가
|
||||||
|
api 'org.springframework.boot:spring-boot-starter-aop'
|
||||||
|
api 'org.aspectj:aspectjweaver:1.9.21'
|
||||||
|
api 'org.aspectj:aspectjrt:1.9.21'
|
||||||
|
|
||||||
|
// JSON Processing
|
||||||
|
api 'com.fasterxml.jackson.core:jackson-databind'
|
||||||
|
api 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
|
||||||
|
|
||||||
|
// JWT
|
||||||
|
api 'io.jsonwebtoken:jjwt-api:0.12.3'
|
||||||
|
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.3'
|
||||||
|
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.3'
|
||||||
|
|
||||||
|
// Apache Commons
|
||||||
|
api 'org.apache.commons:commons-lang3:3.13.0'
|
||||||
|
api 'org.apache.commons:commons-collections4:4.4'
|
||||||
|
|
||||||
|
// OpenAPI/Swagger
|
||||||
|
api 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'
|
||||||
|
|
||||||
|
// Lombok
|
||||||
|
compileOnly 'org.projectlombok:lombok'
|
||||||
|
annotationProcessor 'org.projectlombok:lombok'
|
||||||
|
|
||||||
|
// 테스트
|
||||||
|
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||||
|
testImplementation 'org.springframework.security:spring-security-test'
|
||||||
|
}
|
||||||
|
|
||||||
|
// JAR 생성 설정 (plain jar도 생성)
|
||||||
|
jar {
|
||||||
|
archiveBaseName = 'common'
|
||||||
|
archiveVersion = '1.0.0'
|
||||||
|
enabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// bootJar 비활성화 (라이브러리이므로 실행 가능한 JAR 불필요)
|
||||||
|
bootJar {
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
@ -1,4 +1,52 @@
|
|||||||
package com.ktds.hi.common.config;
|
package com.ktds.hi.common.config;
|
||||||
|
|
||||||
public class AsyncConfig {
|
import org.springframework.context.annotation.Bean;
|
||||||
}
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.scheduling.annotation.EnableAsync;
|
||||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 비동기 처리 설정 클래스
|
||||||
|
* @Async 어노테이션을 위한 스레드 풀 설정
|
||||||
|
*
|
||||||
|
* @author 하이오더 개발팀
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@EnableAsync
|
||||||
|
public class AsyncConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 감사 로그용 스레드 풀
|
||||||
|
*/
|
||||||
|
@Bean("auditTaskExecutor")
|
||||||
|
public Executor auditTaskExecutor() {
|
||||||
|
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||||
|
executor.setCorePoolSize(2);
|
||||||
|
executor.setMaxPoolSize(5);
|
||||||
|
executor.setQueueCapacity(100);
|
||||||
|
executor.setThreadNamePrefix("audit-");
|
||||||
|
executor.setWaitForTasksToCompleteOnShutdown(true);
|
||||||
|
executor.setAwaitTerminationSeconds(30);
|
||||||
|
executor.initialize();
|
||||||
|
return executor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 일반적인 비동기 작업용 스레드 풀
|
||||||
|
*/
|
||||||
|
@Bean("generalTaskExecutor")
|
||||||
|
public Executor generalTaskExecutor() {
|
||||||
|
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||||
|
executor.setCorePoolSize(4);
|
||||||
|
executor.setMaxPoolSize(10);
|
||||||
|
executor.setQueueCapacity(200);
|
||||||
|
executor.setThreadNamePrefix("async-");
|
||||||
|
executor.setWaitForTasksToCompleteOnShutdown(true);
|
||||||
|
executor.setAwaitTerminationSeconds(30);
|
||||||
|
executor.initialize();
|
||||||
|
return executor;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,18 @@
|
|||||||
package com.ktds.hi.common.config;
|
package com.ktds.hi.common.config;
|
||||||
|
|
||||||
public class CommonAopConfig {
|
import org.springframework.context.annotation.Configuration;
|
||||||
}
|
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AOP 설정 클래스
|
||||||
|
* AspectJ 자동 프록시를 활성화
|
||||||
|
*
|
||||||
|
* @author 하이오더 개발팀
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@EnableAspectJAutoProxy
|
||||||
|
public class CommonAopConfig {
|
||||||
|
// AOP 자동 설정을 위한 설정 클래스
|
||||||
|
// @EnableAspectJAutoProxy 어노테이션으로 AspectJ 자동 프록시 활성화
|
||||||
|
}
|
||||||
@ -1,4 +1,76 @@
|
|||||||
package com.ktds.hi.common.service;
|
package com.ktds.hi.common.service;
|
||||||
|
|
||||||
public class AuditAction {
|
/**
|
||||||
}
|
* 감사 액션 열거형
|
||||||
|
* 시스템에서 발생하는 주요 액션들을 정의
|
||||||
|
*
|
||||||
|
* @author 하이오더 개발팀
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public enum AuditAction {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 생성 액션
|
||||||
|
*/
|
||||||
|
CREATE("생성"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 수정 액션
|
||||||
|
*/
|
||||||
|
UPDATE("수정"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 삭제 액션
|
||||||
|
*/
|
||||||
|
DELETE("삭제"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 조회 액션
|
||||||
|
*/
|
||||||
|
ACCESS("조회"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 로그인 액션
|
||||||
|
*/
|
||||||
|
LOGIN("로그인"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 로그아웃 액션
|
||||||
|
*/
|
||||||
|
LOGOUT("로그아웃"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 승인 액션
|
||||||
|
*/
|
||||||
|
APPROVE("승인"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 거부 액션
|
||||||
|
*/
|
||||||
|
REJECT("거부"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 활성화 액션
|
||||||
|
*/
|
||||||
|
ACTIVATE("활성화"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 비활성화 액션
|
||||||
|
*/
|
||||||
|
DEACTIVATE("비활성화");
|
||||||
|
|
||||||
|
private final String description;
|
||||||
|
|
||||||
|
AuditAction(String description) {
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,51 +1,41 @@
|
|||||||
package com.ktds.hi.common.service;
|
package com.ktds.hi.common.service;
|
||||||
|
|
||||||
import com.ktds.hi.common.audit.AuditAction;
|
|
||||||
import com.ktds.hi.common.audit.AuditLog;
|
|
||||||
import com.ktds.hi.common.audit.AuditLogger;
|
|
||||||
import com.ktds.hi.common.repository.AuditLogRepository;
|
|
||||||
import com.ktds.hi.common.security.SecurityUtil;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.scheduling.annotation.Async;
|
import org.springframework.scheduling.annotation.Async;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 감사 로그 서비스
|
* 감사 로그 서비스
|
||||||
* 시스템의 중요한 액션들을 비동기적으로 로깅
|
* 시스템 내 중요한 작업들을 로깅
|
||||||
|
*
|
||||||
|
* @author 하이오더 개발팀
|
||||||
|
* @version 1.0.0
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class AuditLogService {
|
public class AuditLogService {
|
||||||
|
|
||||||
private final AuditLogRepository auditLogRepository;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 감사 로그 기록 (비동기)
|
* 비동기 로그 기록
|
||||||
*/
|
*/
|
||||||
@Async
|
@Async
|
||||||
@Transactional
|
|
||||||
public void logAsync(AuditAction action, String entityType, String entityId, String description) {
|
public void logAsync(AuditAction action, String entityType, String entityId, String description) {
|
||||||
log(action, entityType, entityId, description);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 감사 로그 기록 (동기)
|
|
||||||
*/
|
|
||||||
@Transactional
|
|
||||||
public void log(AuditAction action, String entityType, String entityId, String description) {
|
|
||||||
try {
|
try {
|
||||||
Long userId = SecurityUtil.getCurrentUserId().orElse(null);
|
// 현재 사용자 정보 가져오기 (SecurityContext에서)
|
||||||
String username = SecurityUtil.getCurrentUsername().orElse("SYSTEM");
|
String userId = getCurrentUserId();
|
||||||
|
String username = getCurrentUsername();
|
||||||
|
|
||||||
AuditLog auditLog = AuditLogger.create(userId, username, action, entityType, entityId, description);
|
// 감사 로그 생성 및 저장
|
||||||
auditLogRepository.save(auditLog);
|
log.info("AUDIT_LOG: action={}, entityType={}, entityId={}, userId={}, username={}, description={}",
|
||||||
|
action, entityType, entityId, userId, username, description);
|
||||||
|
|
||||||
|
// 실제 환경에서는 데이터베이스에 저장
|
||||||
|
// AuditLog auditLog = AuditLog.create(userId, username, action, entityType, entityId, description);
|
||||||
|
// auditLogRepository.save(auditLog);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Failed to save audit log: action={}, entityType={}, entityId={}",
|
log.error("Failed to save audit log: action={}, entityType={}, entityId={}",
|
||||||
action, entityType, entityId, e);
|
action, entityType, entityId, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,13 +71,38 @@ public class AuditLogService {
|
|||||||
* 로그인 로그
|
* 로그인 로그
|
||||||
*/
|
*/
|
||||||
public void logLogin(String description) {
|
public void logLogin(String description) {
|
||||||
logAsync(AuditAction.LOGIN, "USER", SecurityUtil.getCurrentUserId().map(String::valueOf).orElse("UNKNOWN"), description);
|
logAsync(AuditAction.LOGIN, "USER", getCurrentUserId(), description);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 로그아웃 로그
|
* 로그아웃 로그
|
||||||
*/
|
*/
|
||||||
public void logLogout(String description) {
|
public void logLogout(String description) {
|
||||||
logAsync(AuditAction.LOGOUT, "USER", SecurityUtil.getCurrentUserId().map(String::valueOf).orElse("UNKNOWN"), description);
|
logAsync(AuditAction.LOGOUT, "USER", getCurrentUserId(), description);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* 현재 사용자 ID 조회
|
||||||
|
*/
|
||||||
|
private String getCurrentUserId() {
|
||||||
|
try {
|
||||||
|
// SecurityContext에서 사용자 ID 추출
|
||||||
|
// 실제 구현에서는 SecurityContextHolder 사용
|
||||||
|
return "SYSTEM"; // 임시값
|
||||||
|
} catch (Exception e) {
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 현재 사용자명 조회
|
||||||
|
*/
|
||||||
|
private String getCurrentUsername() {
|
||||||
|
try {
|
||||||
|
// SecurityContext에서 사용자명 추출
|
||||||
|
return "system"; // 임시값
|
||||||
|
} catch (Exception e) {
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,10 +1,132 @@
|
|||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
id 'org.springframework.boot' version '3.4.0'
|
||||||
|
id 'io.spring.dependency-management' version '1.1.6'
|
||||||
|
}
|
||||||
|
|
||||||
|
group = 'com.ktds.hi'
|
||||||
|
version = '1.0.0'
|
||||||
|
|
||||||
|
java {
|
||||||
|
sourceCompatibility = '21'
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
compileOnly {
|
||||||
|
extendsFrom annotationProcessor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
// 공통 모듈
|
||||||
implementation project(':common')
|
implementation project(':common')
|
||||||
|
|
||||||
// AI and Location Services
|
// Spring Boot Starters
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-webflux'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-actuator'
|
||||||
|
|
||||||
|
// Database
|
||||||
|
runtimeOnly 'org.postgresql:postgresql'
|
||||||
|
|
||||||
|
// Redis
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
|
||||||
|
|
||||||
|
// JSON Processing
|
||||||
|
implementation 'com.fasterxml.jackson.core:jackson-databind'
|
||||||
|
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
|
||||||
|
|
||||||
|
// Lombok
|
||||||
|
compileOnly 'org.projectlombok:lombok'
|
||||||
|
annotationProcessor 'org.projectlombok:lombok'
|
||||||
|
|
||||||
|
// OpenAPI/Swagger
|
||||||
|
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'
|
||||||
|
|
||||||
|
// Logging
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-logging'
|
||||||
|
|
||||||
|
// Security (JWT 처리용)
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||||
|
implementation 'io.jsonwebtoken:jjwt-api:0.12.3'
|
||||||
|
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.3'
|
||||||
|
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.3'
|
||||||
|
|
||||||
|
// Apache Commons (유틸리티)
|
||||||
|
implementation 'org.apache.commons:commons-lang3:3.13.0'
|
||||||
|
implementation 'org.apache.commons:commons-collections4:4.4'
|
||||||
|
|
||||||
|
// AI 서비스 연동을 위한 HTTP 클라이언트
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-webflux'
|
implementation 'org.springframework.boot:spring-boot-starter-webflux'
|
||||||
|
|
||||||
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
|
// 캐싱
|
||||||
implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j'
|
implementation 'org.springframework.boot:spring-boot-starter-cache'
|
||||||
implementation 'org.springframework.cloud:spring-cloud-starter-loadbalancer'
|
implementation 'com.github.ben-manes.caffeine:caffeine'
|
||||||
|
|
||||||
|
// 모니터링
|
||||||
|
implementation 'io.micrometer:micrometer-registry-prometheus'
|
||||||
|
|
||||||
|
// 테스트
|
||||||
|
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||||
|
testImplementation 'org.springframework.security:spring-security-test'
|
||||||
|
testImplementation 'org.testcontainers:junit-jupiter'
|
||||||
|
testImplementation 'org.testcontainers:postgresql'
|
||||||
|
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dependencyManagement {
|
||||||
|
imports {
|
||||||
|
mavenBom "org.testcontainers:testcontainers-bom:1.19.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named('test') {
|
||||||
|
useJUnitPlatform()
|
||||||
|
|
||||||
|
// 테스트 환경 설정
|
||||||
|
systemProperty 'spring.profiles.active', 'test'
|
||||||
|
|
||||||
|
// 테스트 시 메모리 설정
|
||||||
|
minHeapSize = "512m"
|
||||||
|
maxHeapSize = "1024m"
|
||||||
|
}
|
||||||
|
|
||||||
|
// JAR 파일 이름 설정
|
||||||
|
jar {
|
||||||
|
archiveBaseName = 'recommend-service'
|
||||||
|
archiveVersion = '1.0.0'
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
bootJar {
|
||||||
|
archiveBaseName = 'recommend-service'
|
||||||
|
archiveVersion = '1.0.0'
|
||||||
|
archiveClassifier = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 컴파일 옵션
|
||||||
|
compileJava {
|
||||||
|
options.encoding = 'UTF-8'
|
||||||
|
options.compilerArgs += ['-parameters']
|
||||||
|
}
|
||||||
|
|
||||||
|
compileTestJava {
|
||||||
|
options.encoding = 'UTF-8'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 리소스 인코딩
|
||||||
|
processResources {
|
||||||
|
filesMatching('**/*.properties') {
|
||||||
|
filteringCharset = 'UTF-8'
|
||||||
|
}
|
||||||
|
filesMatching('**/*.yml') {
|
||||||
|
filteringCharset = 'UTF-8'
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,19 +2,22 @@ package com.ktds.hi.recommend;
|
|||||||
|
|
||||||
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.cloud.openfeign.EnableFeignClients;
|
|
||||||
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
|
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 추천 서비스 메인 애플리케이션
|
* 추천 서비스 메인 애플리케이션 클래스
|
||||||
|
* 가게 추천, 취향 분석 기능을 제공
|
||||||
|
*
|
||||||
|
* @author 하이오더 개발팀
|
||||||
|
* @version 1.0.0
|
||||||
*/
|
*/
|
||||||
@SpringBootApplication(scanBasePackages = {
|
@SpringBootApplication(scanBasePackages = {
|
||||||
"com.ktds.hi.recommend",
|
"com.ktds.hi.recommend",
|
||||||
"com.ktds.hi.common"
|
"com.ktds.hi.common"
|
||||||
})
|
})
|
||||||
@EnableFeignClients
|
|
||||||
@EnableJpaAuditing
|
@EnableJpaAuditing
|
||||||
public class RecommendServiceApplication {
|
public class RecommendServiceApplication {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(RecommendServiceApplication.class, args);
|
SpringApplication.run(RecommendServiceApplication.class, args);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,80 @@
|
|||||||
package com.ktds.hi.recommend.infra.config;
|
package com.ktds.hi.recommend.infra.config;
|
||||||
|
|
||||||
public class RecommendWebClientConfig {
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
}
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 추천 서비스 WebClient 설정 클래스
|
||||||
|
* 외부 서비스와의 HTTP 통신을 위한 설정
|
||||||
|
*
|
||||||
|
* @author 하이오더 개발팀
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class RecommendWebClientConfig {
|
||||||
|
|
||||||
|
@Value("${services.store.url:http://store-service:8082}")
|
||||||
|
private String storeServiceUrl;
|
||||||
|
|
||||||
|
@Value("${services.review.url:http://review-service:8083}")
|
||||||
|
private String reviewServiceUrl;
|
||||||
|
|
||||||
|
@Value("${services.member.url:http://member-service:8081}")
|
||||||
|
private String memberServiceUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 매장 서비스와 통신하기 위한 WebClient
|
||||||
|
*/
|
||||||
|
@Bean("storeWebClient")
|
||||||
|
public WebClient storeWebClient() {
|
||||||
|
return WebClient.builder()
|
||||||
|
.baseUrl(storeServiceUrl)
|
||||||
|
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(1024 * 1024))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 리뷰 서비스와 통신하기 위한 WebClient
|
||||||
|
*/
|
||||||
|
@Bean("reviewWebClient")
|
||||||
|
public WebClient reviewWebClient() {
|
||||||
|
return WebClient.builder()
|
||||||
|
.baseUrl(reviewServiceUrl)
|
||||||
|
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(1024 * 1024))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회원 서비스와 통신하기 위한 WebClient
|
||||||
|
*/
|
||||||
|
@Bean("memberWebClient")
|
||||||
|
public WebClient memberWebClient() {
|
||||||
|
return WebClient.builder()
|
||||||
|
.baseUrl(memberServiceUrl)
|
||||||
|
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(1024 * 1024))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 일반적인 외부 API 호출을 위한 WebClient
|
||||||
|
*/
|
||||||
|
@Bean("externalWebClient")
|
||||||
|
public WebClient externalWebClient() {
|
||||||
|
return WebClient.builder()
|
||||||
|
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 호환성을 위한 RestTemplate (레거시 지원)
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public RestTemplate restTemplate() {
|
||||||
|
return new RestTemplate();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,150 @@
|
|||||||
package com.ktds.hi.recommend.infra.gateway;
|
package com.ktds.hi.recommend.infra.gateway;
|
||||||
|
|
||||||
public class ExternalServiceClient {
|
import lombok.RequiredArgsConstructor;
|
||||||
}
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.core.ParameterizedTypeReference;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 외부 서비스 클라이언트
|
||||||
|
* 다른 마이크로서비스와의 HTTP 통신을 담당
|
||||||
|
*
|
||||||
|
* @author 하이오더 개발팀
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
public class ExternalServiceClient {
|
||||||
|
|
||||||
|
@Qualifier("storeWebClient")
|
||||||
|
private final WebClient storeWebClient;
|
||||||
|
|
||||||
|
@Qualifier("reviewWebClient")
|
||||||
|
private final WebClient reviewWebClient;
|
||||||
|
|
||||||
|
@Qualifier("memberWebClient")
|
||||||
|
private final WebClient memberWebClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 매장 정보 조회
|
||||||
|
*/
|
||||||
|
public Mono<Map<String, Object>> getStoreInfo(Long storeId) {
|
||||||
|
return storeWebClient
|
||||||
|
.get()
|
||||||
|
.uri("/api/stores/{storeId}", storeId)
|
||||||
|
.retrieve()
|
||||||
|
.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {})
|
||||||
|
.timeout(Duration.ofSeconds(5))
|
||||||
|
.doOnError(error -> log.error("매장 정보 조회 실패: storeId={}, error={}", storeId, error.getMessage()))
|
||||||
|
.onErrorReturn(Map.of());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 매장 목록 조회 (위치 기반)
|
||||||
|
*/
|
||||||
|
public Mono<List<Map<String, Object>>> getStoresByLocation(Double latitude, Double longitude, Integer radius) {
|
||||||
|
return storeWebClient
|
||||||
|
.get()
|
||||||
|
.uri(uriBuilder -> uriBuilder
|
||||||
|
.path("/api/stores/search")
|
||||||
|
.queryParam("latitude", latitude)
|
||||||
|
.queryParam("longitude", longitude)
|
||||||
|
.queryParam("radius", radius)
|
||||||
|
.build())
|
||||||
|
.retrieve()
|
||||||
|
.bodyToMono(new ParameterizedTypeReference<List<Map<String, Object>>>() {})
|
||||||
|
.timeout(Duration.ofSeconds(10))
|
||||||
|
.doOnError(error -> log.error("위치 기반 매장 조회 실패: lat={}, lng={}, radius={}, error={}",
|
||||||
|
latitude, longitude, radius, error.getMessage()))
|
||||||
|
.onErrorReturn(List.of());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 매장별 리뷰 조회
|
||||||
|
*/
|
||||||
|
public Mono<List<Map<String, Object>>> getStoreReviews(Long storeId, Integer limit) {
|
||||||
|
return reviewWebClient
|
||||||
|
.get()
|
||||||
|
.uri(uriBuilder -> uriBuilder
|
||||||
|
.path("/api/reviews/store/{storeId}")
|
||||||
|
.queryParam("limit", limit)
|
||||||
|
.build(storeId))
|
||||||
|
.retrieve()
|
||||||
|
.bodyToMono(new ParameterizedTypeReference<List<Map<String, Object>>>() {})
|
||||||
|
.timeout(Duration.ofSeconds(5))
|
||||||
|
.doOnError(error -> log.error("매장 리뷰 조회 실패: storeId={}, error={}", storeId, error.getMessage()))
|
||||||
|
.onErrorReturn(List.of());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회원 취향 정보 조회
|
||||||
|
*/
|
||||||
|
public Mono<Map<String, Object>> getMemberPreferences(Long memberId) {
|
||||||
|
return memberWebClient
|
||||||
|
.get()
|
||||||
|
.uri("/api/members/{memberId}/preferences", memberId)
|
||||||
|
.retrieve()
|
||||||
|
.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {})
|
||||||
|
.timeout(Duration.ofSeconds(5))
|
||||||
|
.doOnError(error -> log.error("회원 취향 정보 조회 실패: memberId={}, error={}", memberId, error.getMessage()))
|
||||||
|
.onErrorReturn(Map.of());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 회원 주문 이력 조회
|
||||||
|
*/
|
||||||
|
public Mono<List<Map<String, Object>>> getMemberOrderHistory(Long memberId, Integer limit) {
|
||||||
|
return memberWebClient
|
||||||
|
.get()
|
||||||
|
.uri(uriBuilder -> uriBuilder
|
||||||
|
.path("/api/members/{memberId}/orders")
|
||||||
|
.queryParam("limit", limit)
|
||||||
|
.build(memberId))
|
||||||
|
.retrieve()
|
||||||
|
.bodyToMono(new ParameterizedTypeReference<List<Map<String, Object>>>() {})
|
||||||
|
.timeout(Duration.ofSeconds(5))
|
||||||
|
.doOnError(error -> log.error("회원 주문 이력 조회 실패: memberId={}, error={}", memberId, error.getMessage()))
|
||||||
|
.onErrorReturn(List.of());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 매장 평점 및 통계 조회
|
||||||
|
*/
|
||||||
|
public Mono<Map<String, Object>> getStoreStatistics(Long storeId) {
|
||||||
|
return storeWebClient
|
||||||
|
.get()
|
||||||
|
.uri("/api/stores/{storeId}/statistics", storeId)
|
||||||
|
.retrieve()
|
||||||
|
.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {})
|
||||||
|
.timeout(Duration.ofSeconds(5))
|
||||||
|
.doOnError(error -> log.error("매장 통계 조회 실패: storeId={}, error={}", storeId, error.getMessage()))
|
||||||
|
.onErrorReturn(Map.of("rating", 0.0, "reviewCount", 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 인기 매장 조회
|
||||||
|
*/
|
||||||
|
public Mono<List<Map<String, Object>>> getPopularStores(String category, Integer limit) {
|
||||||
|
return storeWebClient
|
||||||
|
.get()
|
||||||
|
.uri(uriBuilder -> uriBuilder
|
||||||
|
.path("/api/stores/popular")
|
||||||
|
.queryParam("category", category)
|
||||||
|
.queryParam("limit", limit)
|
||||||
|
.build())
|
||||||
|
.retrieve()
|
||||||
|
.bodyToMono(new ParameterizedTypeReference<List<Map<String, Object>>>() {})
|
||||||
|
.timeout(Duration.ofSeconds(10))
|
||||||
|
.doOnError(error -> log.error("인기 매장 조회 실패: category={}, limit={}, error={}",
|
||||||
|
category, limit, error.getMessage()))
|
||||||
|
.onErrorReturn(List.of());
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user