Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
47 changes: 47 additions & 0 deletions .github/workflows/ci-java.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: CI (Java)

on:
push:
branches:
- main
paths:
- "apps/user-service/**"
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
branches:
- main
- develop
- release/**
paths:
- "apps/user-service/**"

permissions:
contents: read
packages: write
security-events: write
checks: write
pull-requests: write

jobs:
spotless-check:
name: Lint Check
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
cache: 'gradle'

- name: Grant execute permission for Gradle wrapper
run: chmod +x ./gradlew
working-directory: apps/user-service

- name: Run Spotless Check
run: ./gradlew spotlessCheck
working-directory: apps/user-service
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import time
from typing import Dict, Any, List
from fastapi import Request
from loguru import logger
from contextvars import ContextVar

trace_id_context: ContextVar[str] = ContextVar('trace_id', default="NO_TRACE_ID")

class RepositoryLoggingDependency:
"""
레포지토리 로깅을 위한 의존성 클래스
:param repository_type: 레포지토리 유형 (예: "VECTOR_DB", "RDB", "REDIS")
:param track_params: 추적할 매개변수 이름 목록
"""

def __init__(self, repository_type: str, track_params: List[str] = None):
self.repository_type = repository_type
self.track_params = track_params or []

async def __call__(self, request: Request):
"""
의존성 주입 시 호출되는 메서드
:param request: FastAPI Request 객체
:return: 레포지토리 유형과 추출된 매개변수 딕셔너리
"""
trace_id = trace_id_context.get("NO_TRACE_ID")
start_time = time.time()

# 파라미터 추출
params = await self._extract_params(request)
param_str = ""
if params:
param_strs = [f"{k}={v}" for k, v in params.items()]
param_str = " " + " ".join(param_strs)

logger.info(f"[{self.repository_type}_START] trace_id={trace_id}{param_str}")

# 응답 시 사용할 정보를 request.state에 저장
request.state.repository_type = self.repository_type
request.state.start_time = start_time
request.state.param_str = param_str

return {"repository_type": self.repository_type, "params": params}

async def _extract_params(self, request: Request) -> Dict[str, Any]:
"""
요청에서 추적 파라미터 추출
:param request: FastAPI Request 객체
:return: 추출된 매개변수 딕셔너리
"""
params = {}

try:
# Query Parameters 추출
for key, value in request.query_params.items():
if key in self.track_params:
params[key] = value

# JSON Body 추출
try:
json_body = await request.json()
if json_body:
for key, value in json_body.items():
if key in self.track_params:
if isinstance(value, str) and len(value) > 50:
params[f"{key}_length"] = len(value)
elif isinstance(value, list):
params[f"{key}_count"] = len(value)
else:
params[key] = value
except:
pass
except:
pass

return params


# 레포지토리별 의존성 인스턴스 생성
vector_db_dependency = RepositoryLoggingDependency("VECTOR_DB", ["query", "embeddings", "top_k", "collection", "filters"])
rdb_dependency = RepositoryLoggingDependency("RDB", ["table", "where_clause", "limit", "data"])
redis_dependency = RepositoryLoggingDependency("REDIS", ["key", "value", "ttl", "pattern"])
elasticsearch_dependency = RepositoryLoggingDependency("ELASTICSEARCH", ["index", "query", "size", "document"])


# 응답 로깅을 위한 의존성
async def log_repository_response(request: Request):
"""
레포지토리 응답 시 성공 로그 기록
:param request: FastAPI Request 객체
"""
if hasattr(request.state, 'repository_type'):
trace_id = trace_id_context.get("NO_TRACE_ID")
duration = time.time() - request.state.start_time
logger.info(
f"[{request.state.repository_type}_SUCCESS] trace_id={trace_id} execution_time={duration:.4f}s{request.state.param_str}")
return None


"""
라우터 예시
@router.post("/search")
async def vector_search(
query: str,
top_k: int = 10,
request: Request = None,
_: None = Depends(vector_db_dependency), # 직접 의존성 주입
__: None = Depends(log_repository_response)
):

또는 라우터 레벨에서:
vector_router = APIRouter(
prefix="/vector",
tags=["vector"],
dependencies=[Depends(vector_db_dependency)]
)
"""
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import time
from typing import Dict, Any, List
from fastapi import Request
from loguru import logger
from contextvars import ContextVar

trace_id_context: ContextVar[str] = ContextVar('trace_id', default="NO_TRACE_ID")


class ServiceLoggingDependency:
"""
서비스 로깅을 위한 의존성 클래스
:param service_type: 서비스 유형 (예: "CHUNKING", "PARSING", "EMBEDDING")
:param track_params: 추적할 매개변수 이름 목록
"""

def __init__(self, service_type: str, track_params: List[str] = None):
self.service_type = service_type
self.track_params = track_params or []

async def __call__(self, request: Request):
"""
의존성 주입 시 호출되는 메서드
:param request: FastAPI Request 객체
:return: 서비스 유형과 추출된 매개변수 딕셔너리
"""
trace_id = trace_id_context.get("NO_TRACE_ID")
start_time = time.time()

# 파라미터 추출
params = await self._extract_params(request)
param_str = ""
if params:
param_strs = [f"{k}={v}" for k, v in params.items()]
param_str = " " + " ".join(param_strs)

logger.info(f"[{self.service_type}_START] trace_id={trace_id}{param_str}")

# 응답 시 사용할 정보를 request.state에 저장
request.state.service_type = self.service_type
request.state.start_time = start_time
request.state.param_str = param_str

return {"service_type": self.service_type, "params": params}

async def _extract_params(self, request: Request) -> Dict[str, Any]:
"""
요청에서 추적 파라미터 추출
:param request: FastAPI Request 객체
:return: 추출된 매개변수 딕셔너리
"""
params = {}

try:
# Query Parameters 추출
for key, value in request.query_params.items():
if key in self.track_params:
params[key] = value

# JSON Body 추출
try:
json_body = await request.json()
if json_body:
for key, value in json_body.items():
if key in self.track_params:
if isinstance(value, str) and len(value) > 50:
params[f"{key}_length"] = len(value)
elif isinstance(value, list):
params[f"{key}_count"] = len(value)
else:
params[key] = value
except:
pass
except:
pass

return params


# 서비스별 의존성 인스턴스 생성
chunking_dependency = ServiceLoggingDependency("CHUNKING", ["text", "chunk_size", "overlap"])
parsing_dependency = ServiceLoggingDependency("PARSING", ["file_path", "file_type", "document"])
embedding_dependency = ServiceLoggingDependency("EMBEDDING", ["chunks", "model_name", "batch_size"])

# 응답 로깅을 위한 의존성
async def log_service_response(request: Request):
"""
서비스 응답 시 성공 로그 기록
:param request: FastAPI Request 객체
"""
if hasattr(request.state, 'service_type'):
trace_id = trace_id_context.get("NO_TRACE_ID")
duration = time.time() - request.state.start_time
logger.info(
f"[{request.state.service_type}_SUCCESS] trace_id={trace_id} execution_time={duration:.4f}s{request.state.param_str}")
return None

"""
라우터 예시
@router.post("/chunk")
async def chunk_text(
text: str,
chunk_size: int = 100,
overlap: int = 20,
request: Request = None,
_: None = Depends(chunking_dependency), # 직접 의존성 주입
__: None = Depends(log_service_response)
):
"""
11 changes: 11 additions & 0 deletions apps/pre-processing-service/test_main.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Test your FastAPI endpoints

GET http://127.0.0.1:8000/
Accept: application/json

###

GET http://127.0.0.1:8000/hello/User
Accept: application/json

###
19 changes: 19 additions & 0 deletions apps/user-service/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ plugins {
id 'java'
id 'org.springframework.boot' version '3.5.4'
id 'io.spring.dependency-management' version '1.1.7'
id 'com.diffplug.spotless' version '7.2.1'
}

group = 'com.gltkorea'
Expand Down Expand Up @@ -43,3 +44,21 @@ dependencies {
tasks.named('test') {
useJUnitPlatform()
}

spotless {
java {
googleJavaFormat('1.17.0')
importOrder('java', 'javax', 'org', 'com', '', 'com.movement')
endWithNewline()
removeUnusedImports()
encoding('UTF-8')

targetExclude("**/generated/**", "**/Q*.java")
}
format 'misc', {
target '**/*.gradle', '**/*.md', '**/.gitignore'
trimTrailingWhitespace()
indentWithTabs()
endWithNewline()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
@SpringBootApplication
public class UserServiceApplication {

public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}

public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.gltkorea.icebang.config;

import org.springframework.context.annotation.Configuration;

@Configuration
public class SecurityConfig {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.gltkorea.icebang.domain.user.model;

import java.util.Collection;
import java.util.List;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import lombok.Builder;

@Builder
public class UserAccountPrincipal implements UserDetails {
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of();
}

@Override
public String getPassword() {
return "";
}

@Override
public String getUsername() {
return "";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.gltkorea.icebang.domain.user.service;

import org.springframework.security.core.userdetails.UserDetailsService;

public interface UserAuthService extends UserDetailsService {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.gltkorea.icebang.domain.user.service;

import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import com.gltkorea.icebang.domain.user.model.UserAccountPrincipal;

public class UserAuthServiceImpl implements UserAuthService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return UserAccountPrincipal.builder().build();
}
}
Empty file.
Empty file.
Empty file.
Empty file.
Loading