1515import org .springframework .transaction .annotation .Transactional ;
1616
1717import java .util .List ;
18+ import java .util .concurrent .ConcurrentHashMap ;
1819
1920@ Slf4j
2021@ Service
@@ -26,6 +27,9 @@ public class AnalysisService {
2627 private final RepositoryJpaRepository repositoryJpaRepository ;
2728 private final SseProgressNotifier sseProgressNotifier ;
2829
30+ // 인메모리 락
31+ private final ConcurrentHashMap <String , Boolean > processingRepositories = new ConcurrentHashMap <>();
32+
2933 /* Analysis 분석 프로세스 오케스트레이션 담당
3034 * 1. GitHub URL 파싱 및 검증
3135 * 2. Repository 도메인을 통한 데이터 수집
@@ -38,35 +42,49 @@ public Long analyze(String githubUrl, Long userId) {
3842 String owner = repoInfo [0 ];
3943 String repo = repoInfo [1 ];
4044
41- sseProgressNotifier . notify ( userId , "status" , "분석 시작" ) ;
45+ String cacheKey = userId + ":" + githubUrl ;
4246
43- // Repository 데이터 수집
44- RepositoryData repositoryData ;
47+ // 현재 처리 중인지 체크
48+ if (processingRepositories .putIfAbsent (cacheKey , true ) != null ) {
49+ log .warn ("⚠중복 분석 요청 차단: userId={}, url={}" , userId , githubUrl );
50+ throw new BusinessException (ErrorCode .ANALYSIS_IN_PROGRESS );
51+ }
4552
4653 try {
47- repositoryData = repositoryService .fetchAndSaveRepository (owner , repo , userId );
48- log .info ("🫠 Repository Data 수집 완료: {}" , repositoryData );
49- } catch (BusinessException e ) {
50- log .error ("Repository 데이터 수집 실패: {}/{}" , owner , repo , e );
51- throw handleRepositoryFetchError (e , owner , repo );
52- }
54+ sseProgressNotifier .notify (userId , "status" , "분석 시작" );
5355
54- Repositories savedRepository = repositoryJpaRepository
55- .findByHtmlUrl (repositoryData .getRepositoryUrl ())
56- .orElseThrow (() -> new BusinessException (ErrorCode .GITHUB_REPO_NOT_FOUND ));
56+ // Repository 데이터 수집
57+ RepositoryData repositoryData ;
5758
58- Long repositoryId = savedRepository .getId ();
59+ try {
60+ repositoryData = repositoryService .fetchAndSaveRepository (owner , repo , userId );
61+ log .info ("🫠 Repository Data 수집 완료: {}" , repositoryData );
62+ } catch (BusinessException e ) {
63+ log .error ("Repository 데이터 수집 실패: {}/{}" , owner , repo , e );
64+ throw handleRepositoryFetchError (e , owner , repo );
65+ }
5966
60- // OpenAI API 데이터 분석 및 저장
61- try {
62- evaluationService .evaluateAndSave (repositoryData );
63- } catch (BusinessException e ) {
64- sseProgressNotifier .notify (userId , "error" , "AI 평가 실패: " + e .getMessage ());
65- throw e ;
66- }
67+ Repositories savedRepository = repositoryJpaRepository
68+ .findByHtmlUrl (repositoryData .getRepositoryUrl ())
69+ .orElseThrow (() -> new BusinessException (ErrorCode .GITHUB_REPO_NOT_FOUND ));
70+
71+ Long repositoryId = savedRepository .getId ();
6772
68- sseProgressNotifier .notify (userId , "complete" , "최종 리포트 생성" );
69- return repositoryId ;
73+ // OpenAI API 데이터 분석 및 저장
74+ try {
75+ evaluationService .evaluateAndSave (repositoryData );
76+ } catch (BusinessException e ) {
77+ sseProgressNotifier .notify (userId , "error" , "AI 평가 실패: " + e .getMessage ());
78+ throw e ;
79+ }
80+
81+ sseProgressNotifier .notify (userId , "complete" , "최종 리포트 생성" );
82+ return repositoryId ;
83+ } finally {
84+ // 락 해제
85+ processingRepositories .remove (cacheKey );
86+ log .info ("분석 락 해제: cacheKey={}" , cacheKey );
87+ }
7088 }
7189
7290 private String [] parseGitHubUrl (String githubUrl ) {
0 commit comments